文本处理最强大的应该是perl和sed,对于正则表达式支持得也完备,比如下面是一个匿名人士写给我的感谢信:
lihui@2015 ~$ cat lihui.txt Hello, lihui Thanks Goodbye
可是后来这人发现,谢错人了,需要把lihui修改成lilei,当然此刻我是十分伤悲,顺其自然,能够做到的最简单的方法是用sed
lihui@2015 ~ $ sed -i 's/lihui/lilei/g' lihui.txt lihui@2015 ~ $ cat lihui.txt Hello, lilei Thanks Goodbye
一般来说,perl见得更多的可能是写成脚本.pl形式执行,虽然没有python交互模式,但是perl却可以在命令行直接执行
lihui@2015 ~ $ perl -i -p -e 's/lilei/lihui/g' lihui.txt lihui@2015 ~ $ cat lihui.txt Hello, lihui Thanks Goodbye
瞧,又改回来了,但是要注意的是-e必须放在最后,紧挨着后面执行的程序;而前面的-i和-p顺序随便,-i跟sed里的-i意思一致,将替换的结果写回到文件,-p表示对目标文件每一行进行查找和替换
看到这里,怎么感觉sed和perl语法差不多的样子,这就对了,perl语言出世是个语言专家为了管理结合了sed,awk一堆好东西的特长编写的,所以见怪不怪了,从上面可以看到他们对于文本替换有个共同点,也就是正则表达式的查找和替换特性s/…/…/,简明扼要,意思就是将前面两个//之间的部分替换成后面//之间的内容,实际上在vim里也是能够用到正则表达式替换的,接着上面的例子,通过vim打开lihui.txt进行下面操作
vim lihui.txt :%s/lihui/lilei/g : x lihui@2015 ~ $ cat lihui.txt Hello, lilei Thanks Goodbye
所以说,linux命令除了每天的rm –rf *.o,scp -r *等等,正则表达式无处不在,你时刻都可能在用,只不过自己没发现而已
知道了修改文本的方法之后,具体的修改过程就是修改正则表达式的匹配过程了,比如这里人名作为单独的一个word存在文件当中的,我在Thanks后面添加句“狐狸会撒谎”的拼音hulihuisahuang,其中也包含了lihui,而显然这里不是我们应该修改的人名,那么只需要将lihui作为一个单独的word来进行单独匹配即可,下面两种方式均可
lihui@2015 ~ $ cat lihui.txt Hello, lihui Thanks, hulihuisahuang Goodbye lihui@2015 ~ $ sed -i 's/\<lihui\>/lilei/g' lihui.txt $ perl -i -p -e 's/\blihui\b/lilei/g' lihui.txt lihui@2015 ~ $ cat lihui.txt Hello, lilei Thanks, hulihuisahuang Goodbye
perl里\b的意义和\<\>的意义一致,保证中间的内容是独立的word,而不会包含在其它字符串当中,所以hulihuisahuang不会匹配替换
如果在搜索的时候,忽略大小写,可以在最后添加一个i
lihui@2015 ~ $ sed -i 's/LILEI/lihui/ig' lihui.txt $ perl -i -p -e 's/LILEI/lihui/ig' lihui.txt lihui@2015 ~ $ cat lihui.txt Hello, lihui Thanks, hulihuisahuang Goodbye
接着继续把文本改改,把hulihuisahuang改成1111111111111 times,感激1111111111111次~!有13个1,这样就碰到一个问题,这个数字太长了,到底多少?不太符合大众化的口味,最好能改成我们耳熟能详的三位数三位数逗号相隔开;这里的关键就是需要寻找到某个位置,左边还有数字,右边数字的个数是3的倍数,然后插入逗号,可这明显是从右往左开始查找,而正则表达式是从左往右,这里需要一个新的名词“环视”
环视结构不匹配任何字符,只匹配文本中的特定位置,有点和^,\b类似;顺序型环视从左到右查看文本,成功就返回匹配信息;肯定型顺序环视用特殊序列(?=…)来表示,比如(?=\d)表示如果当前位置右边字符是数字则匹配成功;逆序型环视从右往左查看文本,用序列(?<=…)来表示,比如(?<=\d)表示如果当前位置左边字符是数字则匹配成功
下面一个简单的例子,会很容易弄懂含义
lihui@2015 ~ $ cat lihui.txt Hello, lihui Thanks, 1111111111111 times Goodbye lihui@2015 ~ $ cat lihui.pl #!/usr/bin/perl -w while(<>){ print "Yes1\n" if /(?<=\d{13})/; print "Yes2\n" if /(?<=\d{14})/; print "Yes3\n" if /(?=\d{13})/; print "Yes4\n" if /(?=\d{14})/; } lihui@2015 ~ $ perl lihui.pl lihui.txt Yes1 Yes3
由于一共13个1,所以在逆序查找数字的时候,正好在数字和times的位置的时候,能够匹配到左边有13个数字,所以会打印Yes1;而无法匹配14个数字,所以无法打印Yes2;同样顺序查找也是一样的道理
有了这玩意,再来考虑给13个1按逗号隔开,右边数字个数是3的倍数,左边必须也得有数字,只要有一位数字就能够满足条件,显然(?<=\d)就行了;右边数字个数是3的倍数,只需要(?=\d{3}+\b)就行了,因为不止一个\d{3},而且结尾\b表示word独立结束,试一试
lihui@2015 ~ $ cat lihui.txt Hello, lihui Thanks, 1111111111111 times Goodbye lihui@2015 ~ $ perl -i -p -e 's/(?<=\d)(?=\d{3}+\b)/,/g' lihui.txt lihui@2015 ~ $ cat lihui.txt Hello, lihui Thanks, 1111111111,111 times Goodbye
可惜看样子值匹配了一次,也就是只迭代了一次,这个例子插入了逗号之后就停下来了,没有继续检查前面剩下的数值,所以可以通过一个while来迭代循环,直到匹配失败
lihui@2015 ~ $ cat lihui.txt Hello, lihui Thanks, 1111111111111 times Goodbye lihui@2015 ~ $ cat lihui.pl #!/usr/bin/perl -w while(<>){ while ($_ =~ s/(?<=\d)(?=\d{3}+\b)/,/g) { } print $_; } lihui@2015 ~ $ perl lihui.pl lihui.txt Hello, lihui Thanks, 1,111,111,111,111 times Goodbye
终于,这样就行了
正则表达式一共有4种环视,肯定/否定 顺序/逆序 环视,有兴趣的可以搜下~!