Shell参数展开

引子

在写Shell脚本的时候,不可避免地要用到变量,也常常要对变量进行一些基本的处理。比如说前两天在给人演示*nix系统的方便之处时,我举了个文件名批量修改的例子(好吧。。这是个不怎么恰当的例子。。),比如说我们想把.h结尾的文件改成.hpp,那么可以这么做

 find . -regex ".*\.h$" -exec rename 's/.h$/.hpp' {} \;

原理很简单:先找到以”.h”结尾的文件,然后调用rename将结尾的”.h”改成”.hpp”。但是在演示的时候杯具出现了: Snow Leopard默认是没有rename这个程序的。-.-||| 没办法,只好想其他办法:

find . -regex ".*\.h$" | while read i; do mv $i ${i%.h}.hpp; done;

这个虽然也可以完成任务,但是看上去就比上一条命令复杂多了,尤其是”${i%.h}.hpp”这种用法,显然不如’s/\.h$/\.hpp’看上去直观。虽然之后又想到了其他的替代方案,比如:

find . -regex ".*\.h$" | while read i; do mv $i $(basename $i h)hpp; done;

但像${i%.h}这种用法确实也会经常碰到,于是我就发扬了下打破沙锅问到底的优秀品质,想搞清楚这种类似的用法,到底还有哪些呢?

Shell Parameter Expansion

其实这种用法是有专有名词的,就叫做Shell参数展开(Shell Parameter Expansion)。感兴趣的同学可以直接看GNU Bash Manual中的详细介绍

其实最基本的参数展开像这样:

${parameter}

shell会将上述表达式转换为parameter的值,即我们通常用的$parameter. 其中parameter是参数名,之所以用”{}”括起来是为了确保shell不会将紧跟其后的字符也作为变量名的一部分,从而尽可能地避免杯具的发生。比如说:

$ var="Hello, Bread"
$ echo ${var}
Hello, Bread

细心的同学可能会问其实这里说的参数(parameter)不就是我们通常说的变量(variable)么? 嗯。。其实大部分时候这俩名词的意思基本等同。只不过在Shell中parameter(参数)是variable(变量)的超集: 变量名不能以数字开头,而参数名可以。比如说$1就表示命令行传入的第一个参数。

除去最基本的参数展开之外,还有很多种比较灵活的用法如下:

1. 间接展开

${!parameter}

相当于${var},而var=${parameter}。比如说:

$ param="var"
$ var="Hello, Bread"
$ echo ${!param}
Hello, Bread

2. 参数长度

${#parameter}

作用: Shell会将其替换为$parameter的长度。
例子:

$ var="Hello"
$ echo "len($var) is "${#var}
len(hello) is 5

3. “掐头去尾”

截取又分两种操作,”掐头”和”去尾”。与正则表达式中不同,头和尾在这里对应的符号分别是”#”和”%”。

掐头:

${parameter#word}${parameter##word}

作用: 从parameter头部开始匹配word,并删除成功匹配的部分。在构造word时可以使用”*”表示任意长度字符,”?”表示单位长度字符,并可用形如”[a-c]“的方式来指定匹配”abc”中的任意字符。

另外,”#”和”##”的区别在于前者是最短匹配,而后者是最长匹配;实际上就是正则表达式中的”懒惰”和”贪婪”的概念。下面的例子能够很清楚地看出这两者的区别。比如说:

$ var=brbread
$ echo ${var##*br}
ead
$ echo ${var#*br}
bread

去尾:

${parameter%word}${parameter%%word}

作用: 与掐头相同,唯一不同的是从$parameter的尾部开始匹配。
例子:

$ var="La.Maison.en.Petits.Cubes.avi"
$ echo ${var%.*}
La.Maison.en.Petits.Cubes
$ echo ${var%%.*}
La

匹配示例中的”.*”时, shell会从$var的尾部开始查找”.”,如果是最短匹配(第一个示例),则找到第一个”.”就停止,否则(第二个示例)会一直找到最后一个”.”才停止。可以看到,这种用法可以很方便地去掉文件后缀,从而得到文件名,正是本文开头所用到的方法。

4. 字符串替换

格式:

${parameter/pattern/string}

作用: 将$parameter中出现的第一个pattern替换为string。值得注意的是,除了”*”, “?” 和”[]“以外,pattern的头部还可以使用下面几个字符:

“/”: 如果pattern以”/”起始,则所有的匹配项都要替换。而默认的行为只是替换最左侧的一个。
“#”: 如果pattern以”#”起始,则与正则表达式匹配规则相同,只有在$parameter的头部找到匹配项才会进行替换。
“%”: 与”#”类似,只是这次变成了尾部匹配。

例子:

$ var="see"
$ echo ${var/e/a} #只有第一个e会被替换
sae
$ echo ${var/ee/it}
sit
$ echo ${var//e/a} #所有的e都会被替换
saa
$ echo ${var/#e/a} #头部的e才会被替换
see
$ echo ${var/#s/b}
bee
$ echo ${var/%e/a}
sea

5. 截取子串

格式:

${parameter:offset:length}

作用: 从offset处开始,截取$parameter中长度为length的子串。其中offset如果为负数的话,表示从尾部开始数。并且要注意这时候”:”和offset之间至少要有一个空格,不然shell会当成”:-”处理,这个是后面要介绍到的空参数处理操作符。比如:

$ var="hello"
$ echo ${var:2:2}
ll
$ echo ${var: -3:2}
ll
$ echo ${var:-3:2}
hello

6. 查找参数名

${!prefix*}

Shell会自动将其展开为所有以prefix开头的参数名。如:

$ var1=
$ var2=
$ echo ${!var*}
var1 var2

7. 空参数处理

在这个部分介绍的操作符都是与空参数相关的,而空参数就是值为空,或unset掉的参数。比如说:

$ var=

$ unset var

这样操作后的参数var就是一个空参数。

${parameter:-word}

parameter不为空则用$parameter,不然就用word。例子:

$ unset bread
$ echo ${bread:-hello}
hello

这样实际上是为parameter提供了一个默认值。类似swith..case中的default语句。

 ${parameter:+word}

如果parameter不为空,就返回word。
例子:

$ var="hello"
$ echo ${var:+"var is not null"}

除了上面介绍到的之外,shell参数展开还有很多其他的用法,比如说转换大小写,返回有效的数组下标等等。具体的请参见GNU bash manual.

有趣的非法素数

起因是看到Matrix67推了个Illegal Prime的wiki页面,觉得好奇就看了看。本以为是不合法的素数,类似伪素数那种,但看了wiki才发现这个”illegal”并不是我本来所理解的”invalid”。原来这些个素数之所以被称为”非法”,是因为其中包含一些非法信息。确切一点说,通过某种方法对这些素数进行解码,就可以得到DeCSS的源码。也就是说,传播这种illegal prime实际上等同于传播破解程序DeCSS本身了。

DeCSS是啥呢? DeCSS是一个破解程序,由三个挪威程序员用C编写,可以破解用CSS(Content Scrambling System)加密的DVD影片(嗯,学好C语言不仅可以搞C语言门,还可以当黑客,喔也)。在DeCSS问世之前,DVD厂商通过对DVD影片格式进行加密,可以有效地控制DVD影片的非法复制和传播。大家要看DVD,只能买官方的DVD盘片和DVD机。想要拷贝一张送给朋友或是买盗版光盘,更是门都没有。所以有了DeCSS以后,可想而知广大的人民群众以及英勇的盗版商就high了,不仅盗版电影满天飞,很多无私的开源工作者们也终于可以开发出免费又好用的播放器了。但是DVD片商都哭了 ToT,DeCSS的开发者Jon Lech Johansen也因此被告上法庭(这位小朋友当时才15岁,惭愧),打算借此来遏制一下盗版工作者的嚣张气焰。

但俗话说“哪里有压迫,哪里就有反抗”。这件事情发生以后,已经完全离不开盗版光碟的人们被彻底激怒了,闲人们充分发挥自己的聪明才智,誓于DVD片商死磕到底。具体表现为以各种形式传播DeCSS,比如印到T恤(CopyLeft还因此吃了官司)和领带上啦,做成录音和电影放到网上啦,居然还有猛人直接印到自己高中年鉴里面,当然这其中也包括我们在本文开头所提到的illegal prime的形式了。总之DVD片商是完全陷入了人民战争的汪洋大海之中,要说不打官司兴许还没人知道DeCSS,这下倒好,DeCSS的传播趋势是没法遏制了,而且最后官司也没打赢,天才作者小朋友被无罪释放了。

扯得有点远。。。其实我主要是对用素数来表示源码这个方式比较感兴趣,比如说下文就是第一个被发现的illegal prime:

4856507896573978293098418946942861377074420873513579240196520736
6869851340104723744696879743992611751097377770102744752804905883
1384037549709987909653955227011712157025974666993240226834596619
6060348517424977358468518855674570257125474999648219418465571008
4119086259716947970799152004866709975923596061320725973797993618
8606316914473588300245336972781813914797955513399949394882899846
9178361001825978901031601961835034344895687053845208538045842415
6548248893338047475871128339598968522325446084089711197712769412
0795862440547161321005006459820176961771809478113622002723448272
2493232595472346880029277764979061481298404283457201463489685471
6908235473783566197218622496943162271666393905543024156473292485
5248991225739466548627140482117138124388217717602984125524464744
5055834628144883356319027253195904392838737640739168912579240550
1562088978716337599910788708490815909754801928576845198859630532
3823490558092032999603234471140776019847163531161713078576084862
2363702835701049612595681846785965333100770179916146744725492728
3348691600064758591746278121269007351830924153010630289329566584
3662000800476778967984382090797619859493646309380586336721469695
9750279687712057249966669805614533820741203159337703099491527469
1835659376210222006812679827344576093802030447912277498091795593
8387121000588766689258448700470772552497060444652127130404321182
610103591186476662963858495087448497373476861420880529443

这个素数由搞素数的数学家Phil Carmody发现并公布。但是,如何才能从这个看起来毫无意义的素数得到我们想要的DeCSS源码呢?要想解密,我们需要先来了解下加密的原理:

其实将源码加密成素数的原理很简单。由于在计算机内部所有的数据都是用二进制来表示,自然而然的,任何一段数据都可以被表示成为一个很大的整数,gzip压缩文件也不例外。而且由于gzip格式的文件都是以null字符’\0′来结尾的,在此之后的数据会被忽略,所以我们可以在文件末尾加上冗余来将原本的整数凑成一个素数。上面的这个大素数就是这么凑出来的。

至于具体的凑数方法,也并不复杂。因为根据狄利克雷定理(Dirichlet’s theorem),如果两个数a,b互质,那么序列ka+b (k=1,2,3…)中一定存在无穷多个素数。上面说到DeCSS.gz文件是可以在文件末尾任意加冗余的,因此我们可以通过这一点,将DeCSS.gz文件变成这样的形式:

big_num_repr_gz_file * 2^n + X

Carmody正是用这种方法,一开始n取8, 从1-255穷举X,但都没得到素数。后来又将n取16,终于找到了可行解n=16,X=2083,以及n=1688,X=99。这还没完,这位锲而不舍又写了个程序继续算。-.-# 好了,咱们继续往下说解密吧。。。

解密的时候,当然反过来做一遍就可以了。首先将这个大素数转换成二进制表示,然后以32位分段,每一段用big-endian来编码(就是先存MSB,1表示为’0×1000′),最后去除文件头部可能出现的null字符,就可以看到庐山真面目了。这里是Perl版的解密脚本,因为我不会用Perl(-.-#),又想自己玩玩,所以自己用python写了一个演示程序,也很简单:

#!/usr/bin/python
 
import re, struct
 
prime = int("\
4856507896573978293098418946942861377074420873513579240196520736\
6869851340104723744696879743992611751097377770102744752804905883\
1384037549709987909653955227011712157025974666993240226834596619\
6060348517424977358468518855674570257125474999648219418465571008\
4119086259716947970799152004866709975923596061320725973797993618\
8606316914473588300245336972781813914797955513399949394882899846\
9178361001825978901031601961835034344895687053845208538045842415\
6548248893338047475871128339598968522325446084089711197712769412\
0795862440547161321005006459820176961771809478113622002723448272\
2493232595472346880029277764979061481298404283457201463489685471\
6908235473783566197218622496943162271666393905543024156473292485\
5248991225739466548627140482117138124388217717602984125524464744\
5055834628144883356319027253195904392838737640739168912579240550\
1562088978716337599910788708490815909754801928576845198859630532\
3823490558092032999603234471140776019847163531161713078576084862\
2363702835701049612595681846785965333100770179916146744725492728\
3348691600064758591746278121269007351830924153010630289329566584\
3662000800476778967984382090797619859493646309380586336721469695\
9750279687712057249966669805614533820741203159337703099491527469\
1835659376210222006812679827344576093802030447912277498091795593\
8387121000588766689258448700470772552497060444652127130404321182\
610103591186476662963858495087448497373476861420880529443")
 
result = ''
while prime>0:
    result = struct.pack("!L", prime % (2**32)) + result
    prime /= 2**32
 
result = re.compile('^\0+').sub('',result)
 
file("DeCSS.gz","wb").write(result)

运行该段代码之后,我们就可以得到原来的DeCSS.gz了。用file看下文件属性可以发现:

$ file DeCSS.gz
DeCSS.gz: gzip compressed data, was "css-descramble.c", from Unix, last modified: Mon Feb 26 22:45:45 2001

可以看到,原始的文件叫做css-descramble.c,创建日期也可以看到是01年2月26日。接下来,我们可以直接用vi来查看该压缩文件的内容,或者干脆解压出来:

gunzip -N DeCSS.gz

由于程序太长,就不贴在这里了。实际上该文件还缺少一个用来解密的码表,完整的版本可以看这里

受到Phil Carmody的启发,闲人们又陆续发现了其他素数,并且一个比一个强大。比如说有解密后无需解压直接就是代码的大素数,解密后就是可执行程序能直接使用的大素数(executable prime),感兴趣的同学请移步这里

好累。。连带着又复习了下素数周边的知识,唉,想起来当时学数据安全的时候了,时间过得好快啊~~~

Route config script for MAC OS X

伟大的墙越来越强大了实在是,为了到国外学习先进的科学文化知识来更好地建设有中国特色的社会主义,又必须要访问这些个被Harmonized的网站。唉,觉悟高实在是太累了。好在有VPN。。。 (感谢Paveo同学)。但是由于vpn有流量限制,不得已只好维护一个超大超大的路由表,所有国内的地址都默认沿用原来的网关,剩下的才走vpn。

为了充分发扬”生命在于折腾”的精神,我写了个简单的脚本来做这个工作,每次vpn连接建立以后只要直接运行这个脚本,国内IP段就可以被批量加入到路由表里面了。原理很简单,国内的IP数据库自动从APNIC那里下载过来,然后依次加到路由表中。另外加了些琐碎的功能,比如是否自动更新IP数据库(每次都更新很耗时),可添加自定义IP段(未被墙的IP不用走vpn),大批量清理路由表(有的时候容易添加错 -.-#)等等。脚本这里可以下到,里面有Readme,乱是乱了点,还是能看懂的。

 svn checkout http://ibread.googlecode.com/svn/trunk/rt_vpn rt_vpn

虽然脚本功能很简单,也折腾了我两个晚上的时间。汗,看来时间久了不用确实不行了。而且期间碰到这么几个很抓狂的问题。

第一个就是MAC的route和linux下route用法不太一致,在添加记录时,如果你提供的参数是一个网段,类似这样:

211.160/13

MAC下会自动将其解释为 “211.0.0.160/13″,而不是我们平时所理解的”211.160.0.0/13″。而更抓狂的是在route的manual中明确指出会使用后者。

-net 128.32.130 is interpreted as 128.32.130.0; and 192.168.64/20 is interpreted as -net 192.168.64 -netmask 255.255.240.0

好在隐约记得上次给Paveo同学写脚本的时候碰到过类似的问题,所以另外写了个python脚本转换了下格式(汗,不会awk, sed搞不定,悲剧 -.-#)

另外是在测试的时候,由于众所周知的原因,经常出不来预期的行为,而路由表又总是被蹂躏的乱七八糟。于是自然而然的,在几次失败的测试之后,我们需要清空下路由表,以便以后继续蹂躏。又是按照route的manual所说,直接使用“route flush”便搞定了。但不知道是不是我RP问题,在我的Leopard下,一个是有时需要运行几次才能够完全清空,另一个是清空后几乎每次都会导致vpn连接断掉。最后实在没办法,只好用了个又笨又dirty的方法:我像添加路由条目一样,挨个再把那些表项删除一遍。-.-#

好在终于搞定了,目前用起来没有问题。考虑过几天有时间了再继续整整,Paveo同学的vpn不能保存密码,打算写个Apple script,把连接vpn服务器,输入密码,运行脚本改路由表都给自动化了。另外把断开vpn以及清理路由表也封装下。

不写了,去看paper了~~

New Year, New Hope

新的一年来了,大家纷纷在做09年总结。我想了想,却也不知道自己该写一些什么。09年我基本上一半是海水一半是火焰。上半年在挣扎要不要出国的纠结心态中准备GRE,泡图书馆背单词另外消极怠工鼓捣算法;下半年忙论文忙找工作忙申请,一直到现在还始终没有尘埃落定。想一想真是不堪回首,我有点理解了08年送小鱼姐走之前,她反复告诫我,最好就在一开始就把方向定下来,不要彷徨不要动摇,这样才最好。我又想起来准备GRE时听新东方陈琦录音,他在瞎扯的时候说到,不要瞎担心,别人干啥的时候你也干啥,按部就班好好准备,最后一定会等到好结果的。这些道理我以前就不知道么? 可是我为什么老是踌躇不决?

无论是做了何种选择,现实的问题总是很多。也许我应该抛开所有的琐碎,静下心来问问自己真正想要什么。Avatar里Neytiri在回答Jack为什么要救他是说: “You have a strong heart”。也许我只是不够坚强,没有勇气面对将来,无论前面等着我的是什么。未知的东西总是会引来恐惧是么? 就像我小时候总是害怕仓库后面会有鬼一样,现在想来反而是件很有趣的事情。不知道什么时候我也能微笑着回想起现在的彷徨和纠结呢?

又胡说八道了这么多。回来。许愿吧。

我希望我在新的一年里能够有机会去理想的学校念Ph.D,这应该算是最大的愿望了吧。如果不能如愿,我想我会努力在工作方面做出成绩来,唯一的希望就是能够尽最大可能和自己的兴趣结合在一起吧。其他的,就是希望和GF能够一直走下去。其实现在这样很好,但总还是感觉两人之间有时候少了些什么,不知道是不是两个人太熟悉对方了,反而会更加忽视交流和沟通。我总感觉GF对于我来说越来越像亲人,感情越来越趋于平淡。但无论如何,且行且珍惜吧。也许我根本就不知道我要的是什么,我只是像Sylar一样,永远在觊觎一些我自己都未知的东西。

Andy说, “hope is a good thing, maybe the best of things”. 有希望总归是一件好事。我最后的希望,就是自己能够尽自己最大的努力,让所有的希望都能够达成吧。笑,这是不是个recursive hope。

最后,我还是觉得暂时不把这个blog告诉朋友们,也许这样我可以多写一些私人的东西在这里~

hello wordpress

终于买了域名买了空间了

特别感谢Paveo同学,以及soldiers同学哈哈

希望以后不用花费太多时间在这里,然后就是真的能把这块空间利用起来

就这么多吧,我继续去折腾了哈哈