以m//进行匹配
我们已经在上章简单的使用双斜线的方式来编写模式(注意这里的模式实际上指的就是正则表达式),像/fred/。但实际上,这是m//(模式匹配)操作符的简写。就像我们在说明qw//操作符时提到的,可以选择使用任何成对的定界符。所以,我们可以把同样的表达式写成m(fred)、m<fred>、m{fred}或m[fred]。
这里有个简写:如果你选择双斜线作为定界符,那么你可以省略开头的m。所以大部分的模式匹配都会以双斜线来编写。
用/i来进行行大小写无关的匹配
使用/i修饰符,可以在进行模式匹配时不区分大小写,使得你能够轻易匹配FRED、fred或Fred:
用/s来匹配任意字符
默认情况下,点号(.)无法匹配换行符,这对大多数单行匹配的情况是合适的。但如果字符串中含有换行符,而你希望点号能用来匹配他们,那么/s修饰符可以完成这个任务。它会将模式中的每个点号按字符集[\d\D]的效果来处理,就是会匹配任意字符(包括换行符)。下面的实例就是一个包含换行符的字符串,来看看:
if ( /Barney.*fred/s ) {
print "That string mentions fred after Barney!\n" ;
}
因为这两个名称并不在同一行,所以如果省略/s修饰符,上述的匹配就会失败。
用/x加入空白
说白了这个修饰符就是为了在模式中加入注释使用的,如果一个很复杂的模式不加入注释的话可能时间长了自己写的模式都会不认识了。如下例子:
-? # 零个或一个减号
\d + # 一个或多个数字
\ .? # 零个或一个小数点
\d * # 零个或多个数字
/x # 字符串结尾
组合选项修饰符
如果在一个模式中使用多个修饰符,可将它们连在一起使用,它们之间的先后顺序并不会影响匹配的结果
if ( /Barney.*fred/is ) {
print "That string mentions fred after Barney!\n" ;
}
将同样的模式展开并加上注释后的模样:
锚位
默认情况下,模式匹配的过程开始于待匹配字符串的开头,如果不相符就一直往字符串后面浮动,看其他位置能否匹配。但是加入一些锚位,可以让模式直接匹配字符串的某处。
脱字符(^)用来标示字符串的开头
美元符号($)用来标示字符串的结尾
/^fred/只匹配位于字符串最前端的fred,如果是manfred mann这个字符串,则不能匹配。而/rock$/也只匹配位于字符串最后面的rock,如果是knute rockne,也同样失败。
某些时候,这两个锚位会一起使用,以确保模式可以匹配整个字符串,这个常见的实例是/^\s*$/,这个模式可以匹配空白行。
单词锚位
锚位并不局限于字符串的首尾。比如\b是单词边界锚位,它匹配任何单词的首尾。因此/\bFred\b/可以匹配fred,但无法匹配frederick、alfred或manfred mann。这在文字处理器的搜索命令里,通常称为证词搜索模式。
不过,这里所说的单词并不是一般的英文单词,而是由一组\w字符构成的字符集,也就是有普通英文字母、数字与下划线组成的单词。\b锚位匹配的是一组\w字符的开头或结尾。
在下图中,每个此下发会出现灰色下划线,\b会匹配的位置则以箭头标识。因为每个单词都会有开头与结尾,所以字符串中的单词边界一定是偶数个。
用\b匹配单词边界
此处所谓的单词是指一连串的字母、数字与下划线的组合,也即是匹配/\w+/模式的字符串。该句子共有5个单词:That、s、a、word以及boundary。word两边的引号并不会改变单词边界。这些单词是由一组\w字符组成的。
非单词边界锚位是\B,他能匹配所有\b不能匹配的位置。因此模式/\bsearch\B/会匹配searches、searching与searched,但不会匹配search或researching。
绑定操作符
默认情况下模式匹配的对象是$_,绑定操作符=~则能让Perl拿右边的模式来匹配左边的字符串,而非匹配$_。例如:
if ( $some_other =~ /\brub/ ) {
print "Aye,there's the rub.\n" ;
}
绑定操作符虽然看起来有些像赋值操作符,但它表示不要将模式匹配$_而是将模式匹配我左侧的字符串吧!
下面的这个例子,$likes_perl会被赋予一个布尔值,这个结果取决于用户输入的内容。这个程序属于“急功近利”型的,因为判断之后就丢弃了用户的输入。下面的代码的功能是读取输入行,匹配字符串与模式,然后舍弃输入行的内容。没有进一步使用$_,也没有改变它。
因为绑定操作符的优先级相当高,也就没有必要用圆括号来括住模式测试表达式。所以下面这一行如同上面的表达式一样,会将匹配结果(而非该行输入的内容)存进变量:
my $likes_perl = =~ /\byes\b/i;
模式串中的内插
正则表达式里可以进行双引号形式的内插。这让我们可以很快写出如下类似grep的程序:
use strict ;
my $what = "zyq" ;
while ( <> ) {
if ( /^($what)/ ) {
print "Yes you are $what\n" ;
}
}
无论$what的内容时什么,当我们进行模式匹配的时候,该模式都会成为$what的值。这里和/^zyq/是相同的意思,也即是在每行的开头寻找zyq。但是,$what不一定来自字符串直接量,我们可以从@ARGV里的命令行参数来取得:
my $what = shift @ARGV;
如果命令行的参数是zyq|wcl,则模式会变成/^(zyq|wcl)/,也就是在每一样的开头寻找zyq或wcl。这个模式中的括号很重要,如果没有它们,模式就会在字符串的开头匹配zyq或者在字符串的任何地方匹配wcl。
注意:请记住,除非while循环的条件表达式中只有整行输入操作符(<STDIN>),否则输入行不会自动存入$_
捕获变量
到目前为止,我们在模式中使用括号的时候只是为了区分不同的模式组。但括号在模式中还有捕捉变量的功能。所谓的捕捉变量实际上指得是,把(圆括号中模式所匹配的)部分字符串暂时记下了的能力,如果有一对以上的圆括号,就会有一次以上的捕捉。每个被捕捉的对象是原本的字符串,而不是模式。
因为捕捉变量存储的都是字符串,所以它们都是标量变量。在Perl里它们的名字类似$1或者$2。模式里的括号有多少对,匹配变量就有多少个。
这些变量可以去除字符串里的某些部分,因此是正则表达式威力强大的重要原因之一:
if ( /\s(\w+),/ ) { # 捕获空白符合逗号之间的单词
print "the word was $1\n" ; # the word was there
}
当然也可以一次捕获多个字符串:
上面的程序输出的单词时Hello there neighbor,但输出时没有逗号的,因为模式里的逗号放在圆括号的外面,所以第二次捕捉不会有逗号。使用这个技巧,我们可以精确筛选捕获的(和跳过的)数据。
捕获变量的生命周期
捕获变量只应该在匹配成功时使用;否则就会得到之前一次模式匹配的捕获内容。下面的模式匹配失败的例子本来应该输出从$_捕获的某个单词。但是,如果比对失败,他会输出可能遗留在$1里的任何字符串:
$wilma =~ /(\w+)/; # 这里的结果不一定正确
print "Wilma's word was $1... or was it?\n";
这就可以看出,为什么模式一般都要写在if或while的条件表达式中
捕获的内容不能够永久保存,因此像$1之类的匹配变量如果我们在后面的程序中想要使用其里面的内容,那么最好的方法就是将它赋值给其他定义好的变量,这么做也使得程序代码更容易阅读:
if ($wilma =~ /(\w+)/) {
my $wilma_word = $1;
...
}
不捕获模式
既然括号在模式中有分组和捕获的功能,那如果我在一个模式中即想用分组,又想用捕获,但我却不想捕获第一个本意为分组的内容,那又怎么办呢?当然我可以使用$2、$5之类的变量来代替,但若我就想用$1、$2又当如何处理呢?看看下面的例子:
if (/(?:bronto)?saurus (steak|burger)/) {
print "Fred wants a $1\n";
}
正如上面的例子所示,使用(?:)的形式就可以不捕获这个模式了!
命名捕捉
上面已经讲了可以使用括号的捕捉能力并且在$1、$2这样的变量中存取捕捉的串。但是即使对于较为简单的模式来说,管理这样的数字变量也是比较困难的,更别说大的模式匹配了。所以要想一个便于识别的变量来代替。在Perl 5.10引入了正则表达式命名捕捉的概念。它会将捕捉的记过存入一个特殊的哈希%+,其中的键就是在捕捉时候使用的特殊标签,其中的值则是被捕捉的串。
为捕获串加标签的方法:(?<LABEL>PATTERN)
这里的LABEL可以自行命名,下面的例子中我将第一个捕捉标签定为name1,第二个为name2。当然在使用捕捉串的时候需要访问的位置也变成了$+{name1}和$+{name2}
my $names = 'Fred or Barney' ;
if ( $names =~ m/((?<name2>\w+) (and|or) (?<name1>\w+))/ ) {
say "I saw $+{name1} and $+{name2}" ;
}
自动匹配变量
除了上面介绍的捕获变量外还有3个另类的匹配变量。他们是:$&、$`、$' 含义如下:
$& 字符串里实际匹配模式的部分自动存入该变量
$` 匹配起始位置之前的内容存放在改变量
$' 匹配结束位置之后的内容存放在该变量
看看下面的实例:
输出结果为:That was (Hello)( there,)(neighbor).
使用自动匹配的弊端
如果在程序中使用了自动匹配变量,则其他正则表达式的速度会变慢,虽然不会严重的拖慢速度,但很多程序员还是不怎么喜欢使用这些变量。通常使用带编号的匹配变量也就足够了。本书的后面章节实例中还会看到这些变量的使用。
通用量词
模式中的量词代表前置条目的重复次数。目前已经看到的有:*、+和?。在这里我们再介绍一个({})花括号,花括号里指定重复次数的范围。因此:
/a{5,15}/ 可以匹配重复出现5到15次的字母a
/(fred){3,}/ 这个会匹配重复出现3次以上的fred
/\w{8}/ 会匹配正好8个字符的单词串。注意是8个字符并不是说8个重复的字符
事实上,我们之前提到的三个量词字符都只是常用的简写而已。星号相当于{0,},加号相当于{1,},问号相当于{0,1}。
=====================本章习题=========================
1、利用模式测试程序,写个模式,使其能够匹配到match这个字符串,你可以把beforematchafter输入到程序里测试,看是否会正确显示匹配到的部分以及前后的部分?
答:exercise_8_1.pl
use strict ;
$_ = "beforematchafter" ;
if ( /(match)/i ) {
print "$`\n$1\n$'\n" ;
}
输出结果为:
before
match
after
2、利用模式测试程序,写个模式,使其能够匹配任何以字母a结尾的单词(以\w组成的单词)。此模式是否能够匹配到wilma?是否无法匹配到barney?此模式是否能够匹配到Mrs._Willma_Flintsone?还有wilma&fred呢?把这些测试字符串加到该文本文件里测试程序。
答:exercise_8_2.pl
letter.txt
barney
Mrs. Wilma Flintstone
wilma&fred
输出结果为:
wilma in wilma
Wilma in Mrs. Wilma Flintstone
wilma in wilma&fred
3、修改上一题的程序,使其定在定位以a结尾的单词后,在将之后的5个字符(如果有那么多的话)捕获至一个独立的内存变量。修改程序输出,把所用到的这两个内存变量都输出来。
答:exercise_8_3.pl
use strict ;
while ( <> ) {
if ( /(\b\w*a\b)(.{0,5})/s ) {
print "$1 after is $2 in $_\n" ;
}
}
输出结果为:
wilma after is # 这里实际上是一个换行符
in wilma
Wilma after is Flin in Mrs. Wilma Flintstone # Flin的前面还有一个空格
wilma after is &fred in wilma&fred
Wilma after is yest in I saw Wilma yesterday # yest前面也有一个空格
4、写个新程序,输出其输入中以空白结尾的行。
答:exercise_8_3.pl
输出结果
abcde
no space!
okok_ # 这里最好的下划线实际上是一个空格,这里为了方便查看
you input string end is space!