⑴匹配模式
我们已知在Perl中正则表达式被称为模式,这种模式(也即正则表达式)可以放在由成对符号(例如()、<>、{}等)或者一对不成对的符号(例如//、!!、^^等)组成的界定符内,并在界定符前用小写字母指定模式的种类。当然我们不希望界定符和正则表达式的符号有所冲突(如果实在有冲突可以使用反斜杠转义),事实上最常用的界定符为双斜杠//。在Perl中有很多处理模式,其中最简单的为匹配模式m//,或者也可以理解为查找模式。由于正则表达式本身就有匹配的含义,以双斜杠作为定界符时m可以省略。其他处理模式详见下一小节。
关于Perl正则匹配一个简单的例子如下所示:
代码语言:javascript复制$_ = "yabba dabba doo";
if (/y(.)(.)21/) {
print "It matched!n";
}
运行结果如下所示:
在if的圆括号内默认匹配的是变量$_的内容,因此这段程序实际上是下面所示:
代码语言:javascript复制$_ = "yabba dabba doo";
if ($_ =~ /y(.)(.)21/) {
print "It matched!n";
}
其中=~是表示内容匹配的绑定操作符,其返回值为表示是否成功匹配的布尔值,基于上面的写法我们可以根据实际需要随意改变要匹配的变量名称。
⑵模式修饰符
除了在界定符前可以指定处理模式,在界定符之后还可以添加小写字母修饰符。这些修饰符也叫标志(flag),用来改变默认的匹配行为,正如上一小节展示的使用ASCII编码的//a一样。常用的匹配模式下的修饰符有以下几种:
上面表格中不同的模式修饰符可以进行组合使用,而且其顺序对模式没有影响,如下所示:
代码语言:javascript复制/abc.*xyz/is #忽略大小写并使点号匹配任意字符
⑶锚位
从Perl 5开始,脱字符^和$表示行首和行尾的锚位,这对行输入的数据非常有用,因为行输入的字符串有且只有一个换行符在末尾。对于具有多个换行符的字符串,也即多行文本数据变量,可以使用//m修饰符,使得脱字符^和$可以同时锚定字符串开头、每一行开头、字符串结尾、每一行结尾,如下所示:
代码语言:javascript复制$_ = "This is the wilma line
barney is on another line
but this ends in fred";
if (/^barney/m) {
print "It matched!n";
}
上面代码中如果不加//m修饰符则^只会匹配字符串开头从而匹配失败。此外还有另一种更严谨的锚位方法,使用A、Z、z锚定字符串的开头、每一行末尾、字符串结尾。需要注意的是对于行输入的单行字符串来说Z、z也是完全不同的,Z会匹配换行符前的内容,而z匹配字符串结尾(包括换行符)内容。其使用方法如下所示:
代码语言:javascript复制/Abarney/ #匹配字符串绝对开头位置的barney
/fredz/ #匹配字符串绝对末尾位置的fred
/fredZ/ #匹配行尾也即换行符前的fred
/As*Z/ #匹配一个空行
除了字符串、行的首尾,一个单词的首尾可以使用b进行锚位,这里的单词指的是w字符集也即[a-zA-Z0-9_]组成的字符串,b根据出现的非w字符(包括字符串的绝对首尾位置)判断单词的边界,如下所示:
代码语言:javascript复制/bfredb/ #会匹配fred、fred's但是不会匹配afred、fred_s
此外B则会锚定非单词边界,如下所示
代码语言:javascript复制/bfredB/ #会匹配fred_s 但是不会匹配fred、fred's、afred
⑷变量内插
与双引号内部的变量内插一样,正则表达式内部也可以使用各种数据变量,从而更好的融合到Perl程序之内。正则表达式一般将变量放在括号内(这是和反向引用类似的),例如下面一段类似于grep工具命令的小程序:
代码语言:javascript复制my $what = <STDIN>;
chomp $what;
while (<>) {
if (/A($what)/) {
print "$_";
}
}
上面程序中通过键盘输入$what的值,正则表达式会根据$what的值对命令行参数指定的文件的每一行开头进行匹配,匹配成功则输出该行内容。$what可以是任何值,甚至是正则表达式元字符,如下所示:
⑸捕获变量
在上一小节正则表达式的模式分组中,我们知道圆括号通常会触发正则表达式捕获相匹配的字符串以供反向引用。事实上,Perl会自动将这些圆括号内的捕获组储存在称为捕获变量的标量变量里面,其变量名与反向引用的编号一样都是数字,其命名与捕获组编号相同,也即$1、$2…。模式当中有多少圆括号,就有多少捕获变量,这些变量在正则表达式匹配完成之后仍可以使用,捕获变量是Perl正则表达式强大的原因之一。一个简单的示例如下所示:
代码语言:javascript复制$_ = "Hello there, neighbor";
if (/(S ).*,s(w )/) {
print "What I said is:n$1 $2!n";
}
运行结果如下所示:
这些捕获变量在下一次正则表达式成功匹配之前都是有效的,如果某次匹配失败,那么捕获变量里储存的仍是上一次成功匹配时的数据,这里的匹配成功指的是整个模式的匹配而非捕获组的匹配,这也是模式匹配以及捕获变量的使用一般在if和while等布尔值控制结构里面的原因。如果想永远使用某次捕获的内容,则可以使用捕获变量为自定义标量变量赋值。
尽管我们有多种办法避免在程序维护的时候使捕获组编号错乱,例如使用只具有模式分组功能的圆括号,但是使用顺序编号的捕获变量名称仍会带来很多麻烦。从Perl 5.10开始,允许用户为捕获变量自定义命名,称为标签(label),其写法为在相应捕获组括号里最开头添加问好?和label,也即(?<label>正则表达式)。最终捕获内容会被储存在特殊的哈希% 里面,其key即label,value为括号内正则表达式匹配的内容,可以采用访问哈希% 的方法来使用捕获变量,使用自定义label改写前面的程序如下所示:
代码语言:javascript复制$_ = "Hello there, neighbor";
if (/(?<name1>S ).*,s(?<name2>w )/) {
print "What I said is:n$ {name1} $ {name2}!n";
}
其运行效果与前面相同。同理,在正则表达式里的反向引用可以使用g{label}或者k{label}。此外,Perl还有三个自动捕获变量,其中$&内储存的是正则表达式匹配的全部内容,$`内储存的是匹配区段之前的内容,$'内储存的是匹配区段之后的内容。这三个捕获变量可以随意使用,但代价是会使程序运行变慢。在Perl 5.10及以上的版本,这三个变量有另一种更形象的写法${^PREMATCH}、${^MATCH}、${^POSTMATCH}。