用正则表达式来处理文本
s/Barney/Fred/; #把Barney替换为Fred
print "$_\n";
模式串与替换串还可以更加复杂:
s/with (\w+)/against $1's team/;
print "$_\n"; #"He's out bowling against Fred's team tonight."
s/(\w+) (\w+)/$2, $1/; #替换后为"scaly, green dinosaur"
s/^/huge,/; #替换后为"huge, scaly, green dinosaur"
s/,.*een//; #空替换,此时为"huge dinosaur"
s/green/red/; #匹配失败:仍为"huge dinosaur"
s/\w+$/($`!)$&/; #替换后为"huge (huge !)dinosaur"
s/\s+(!\W+)/$1 /; #替换后为"huge (huge!) dinosaur"
s/huge/gigantic/; #替换后为"gigantic (huge!) dinosaur"
s///返回的是布尔值,替换成功时为真,否则为假: |
s/^\s+|\s+$//g #去除开头和结尾的空白符
《Mastering Regular Expression》
不同的定界符,就像m//与qw//一样,我也可以改变s///的定界符。但是替换运算会用以三个定界符,所以情况有点不同。
s#^https://#http://#; #以井号作为定界符
如果是使用有左右之分的成对字符,就必须使用两对:一对圈引模式,一对圈引替换字符串。下面三行是一样的意思。
s{fred}{barney}
s[fred](barney);
s<fred>#barney#;
可选修饰符:
s#wilma#Wilma#gi; #将所有的WiLmA或者WILMA等一律替换为Wilma
s{_END_.*}{}s; #将_END_标记和其后所有的内容都截掉。
绑定操作符:
$file_name =~ s#^.*/##s; #将$file_name中所有的Unix风格的路径全部去除
大小定转换:
$_ = "I saw Barney with Fred.";
s/(fred|barney)/\U$1/gi; # $_现在成了"I saw BARNEY with FRED."
s/(fred|barney)/\L$1/gi; # $_现在成了"I saw barney with fred."
默认影响之后全部的替换字符串,也可以用\E结束大小写转换的影响:
s/(w+) with (\w+)/U$2\E with $1/i; # $_替换后为"I saw FRED with barney."
使用小写形式(\l与\u)时,它们只会影响之后的第一个字符:
s/(fred|barney)/\u$1/ig; # $_替换后为"I saw FRED with Barney."
它们也可以并用,同时使用\u与\L表示全部转小写,但首字母大写:
s/(fred|barney)/\u\L$1/ig; # $_现在成了" I saw Fred with Barney." #注意\u\L的位置没有影响。
split操作符:
split操作符会根据分隔符拆开一个字符串。这对处理被制表符、冒号、空白或任意符号分隔的数据相当有用。用法如下:
@fields = split /separator/, $string;
split操作符用拆分模式串”扫过“指定的字符串,并返回字段(也就是子串)列表。期间只要模式在某处匹配成功。该处就是一个字段的结尾、下一个字段的开头。
@fields = split /:/, "abc:def:g:h"; #得到 ("abc", "def", "g", "h")
如果两个分隔符连在一起,就会产生空字段:
@fields = split /:/, "abc:def::g:h"; #得到 ("abc", "def", "","g", "h")
这里有个规则,split会保留开头处的空字段,并省略结尾处的空字段。如果要保留结尾处的空字段,只要以-1作为split的第三个参数说明就能保留它们了。
利用/\s+/模式进行空白分隔也是常见的做法。在些模式下,所有的空白会被当成一个空格来处理:
my $some_input = "This is a \t test.\n";
my @args = split /\s+/, $some_input; #("This", "is", "a", "test.")
split默认会以空白字符分割$_:
my @fields = split; #等效于 split /\s+/, $_;
这几乎就等于以/\s+/为模式,只是它省略开头的空字段。
join函数:
join函数不会使用模式,它功能与split恰好相反:split会将字符串分解为数个片段(子字符串),而join则会把这些片段联合成一个字符串。
my $result = join $glue, @pieces;
my $x = jion ":", 4, 6, 8, 10, 12; # $x 为 ”4:6:8:10:12"
my @values = split /:/, $x; #@values为(4, 6, 8, 10, 12)
my $z = join "-", @values; #$z 为 "4-6-8-10-12"
列表上下文中的m// :
在列表上下文中使用模式匹配操作符(m//)时,如果模式匹配成功,那么返回的是所有捕获变量的列表;如果匹配失败,则会返回列表:
$_ = "Hello there, neighbor!";
my ($first, $second, $third) = /(\s+) (\s+), (\s+)/;
print "$second is my $third\n";
my $text = "Fred dropped a 5 ton granite block on Mr. Slate";
my @words = ($text =~ /([a-z]+)/ig);
print "Result: @words\n";
#打印:Result: Fred dropped a ton granite block on Mr Slate
更强大的正则表达式:
非贪婪量词:
我们前面二章看到的4个量词全都是贪婪量词。也就是说在保证整体匹配的前提下,它们会尽量匹配长字符串,实在不行才会吐出一点。如:
/fred.+barney/来匹配fred and barney went bowling last night这个字符串。
这里的+会在匹配完fred时,它人匹配换行符之外的所有字符(至少一次)。
因为加号量词(+)是个贪婪的量词,它会尽量匹配多的字符串。所以它会一路匹配到night,
而使barney没有办法进行匹配了。但+匹配完后,现在轮到模式中的barney部分,但是它已经没办法进行匹配,因为刚才已经进行到字符串的最后面。此时,.+模式就会很不情愿的吐出一个字符,反正就算少了一个字符,这部分模式还算是匹配成功。(虽然它很贪婪,不过为了顾全大局,让整体匹配都成功,就算自己没有匹配到全部的字符串也可以忍受。).+匹配的部分一路减少到了barney之前。于是整个模式也就匹配成功了。
上面为什么说这么多呢。因为这样我们可以知道回溯的动作是非常繁琐的。因为量词囫囵天下太长的字符串,所以效率不高。
/fred.+?barney/来匹配fred and barney went bowling last night这个字符串。
这里的?它会匹配一个以上的字符,但是越短越好。也就是最好一个字符。所以它匹配的部分是fred后面的空白符,接下来出现的barney模式就会失败。而.+?模式又很不情愿地多匹配了一个字符,然后把控制权交给之后的模式重试,但barney还是匹配失败。所以.+?只好再吞下一个字符n直到barney匹配上了。这个模式也就匹配成功了。
上面这两个这个例子就说明。从前面匹配比从后面匹配效率高了很多。
如果要处理的数据都是fred在字符串开始处,barney在字符串尾。那么选用贪婪的量词反而会比较快。
所以最终速度其实取决于正则表达式处理的数据。
非贪婪的量词并不只跟效率有关。尽管只要贪婪版本可以成功匹配的字符串,它们也同样可以匹配成功(匹配失败的情况也是一样的),但是它们匹配的字符串长度是不同的。
I'm taking about the cartoon with Fred an <BOLD>Wilma</BOLD>!
我们可以用s#<BOLD>(.*)</BOLD>#$1#g来替换掉。这里没有问题。但是我们来看下面:
I thought you said Fred and <BOLD>Velma</BOLD>, not <BOLD>Wilma</BOLD>
如果还用上面的贪婪量词来匹配的话。该模式就会第一个<BOLD>匹配直到最后一个</BOLD>,把这中间的部分全部取出来了。这就错了。
像这种情况我们就需要用非贪婪量词s#<BOLD>(.*?)</BOLD>#$1#g;
问号也有非贪婪的版本:??,虽然这还是一样会匹配到一次或零次,但会优先考虑零次的情况。
跨行的模式匹配:
传统的正则表达式都是用来匹配单行文本。由于Perl可以处理任意长度的字符串,其模式匹配也可以处理多行文本,与处理单行文本无差异。
$_ = "I'm much better\nthan Barney is\nat bowling,\nWilma.\n";
^和$通常来匹配整个字符串的开始和结束,但是当模式加上/m修饰之后,就可以让它们也匹配串内的换行符。
print "Found 'wilma' at start of line\n"; if /^wilma\b/im;
=========================================
open FILE, $filename
or die "Can't open '$filename': $!";
my $lines = join '', <FILE>;
$lines =~ s/^/$filename: /gm;
一次更新多个文件:
程序化地更新文件内容时,常见的做法就是先打开一个新文件,然后把跟旧文件相同的内容写进去,并且在需要的位置进行改写。后面会看到,这样做和直接更新文件的做法效果大致相同。
如:我们现在有几百个格式类似的文件。其中一个叫做fred03.dat,里面都是如下几行的内容:
Program name: granite
Author: Gilbert Bates
Company: RockSoft
Department: R&D
Phone: +1 503 555-0095
Date: Tues March 9, 2004
Version: 2.1
Size: 21k
Status: Final beta
我们必须修改这个文件,让它有一些新的信息。如下:
Program name: granite
Author: Randal L. Schwartz
Company: RockSoft
Department: R&D
Date: June 12, 2008 6:38 pm
Version: 2.1
Size: 21k
Satus: Final beta
我们要做三项改动:Author Date 要改。phone则要删除。
要在Perl中直接修改文件内容可以使用钻石操作符(<>),些程序的新意在于特殊变量$^I的使用。
#!/usr/bin/perl -w |
钻石操作符他会自动帮你打开许多文件,而且如果没有指定文件,它就会从标准输入读进数据。但如果$^I中是个字符串,该字符串就会变成备份文件的扩展名。
先假设钻石操作符正好打开了文件fred03.dat。除了像以前一样打开文件之外,它还会把文件名改成fred03.bat.bak。虽然打开的是同一个文件,但是它在磁盘上的文件名已经不同了。接着,钻石操作符会打开一个新文件并将它取名为fred03.dat。这么做并不会有任何问题。因为我们已经没有同文件了。现在钻石操作符会把默认的输出设定为这个新打开的文件,所以输出来的所有内容都会被写进这个文件。
从命令行进行在线编辑:
perl -p -i .bak -w -e 's/Randall/Randal/g' fred*.bat
以Perl开头的命令作用如同在文件的开头写上#!/usr/bin/perl表示以perl程序来处理随后的脚本。
-p选项可以让Perl自动生成一小段程序,看起来类似如下的片段
while (<>) {
print;
}
如果不需要这么多功能,还可以用-n选项,这样可以把自动执行的print去掉。
下一个出现的选项是-i .bak,其作用就是在程序开始运行之前把$^I设为.bak。
-w选项是打开警告的意思。
-e选项用来告诉Perl后面跟着的是程序代码。也就是说,s/Randall/Randal/g这个字符串会被直接当成Perl程序代码。因为目前我们已经有个while循环(来自-p选项)。
以上所有片段组合在一起,就好像写了下面这个程序,并且用fred*.dat这个参数调用它一样:
#!/usr/bin/perl -w |