正则表达式修改文本

文本处理最强大的应该是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种环视,肯定/否定 顺序/逆序 环视,有兴趣的可以搜下~!

发表回复