简单的单词匹配
最简单的正则就是一个单词,或者更通用一点,一个字符序列.一个仅包含某个单词的正则可以匹配包含那个单词的任意字符串:
"Hello World" =~ /World/; # 匹配成功
在这条语句中,World
就是一个正则,/World/
两边的双斜杠//
告诉Perl这是一个正则表达式.运算符=~
会把任意的字符串传递给该正则表达式,如果正则匹配成功,则返回真,如果匹配失败,则返回假.在上面的例子中,World
刚好匹配了字符串"Hello World"
中的第二个单词,所以该表达式返回的值为真.
类似的这种表达式经常会被用在条件判断语句中:
print "It matches " if "Hello World" =~ /World/;
还可以使用!~
运算符来表示相反的意思,也就是"不匹配":
print "It doesn't match " if "Hello World" !~ /World/;
正则表达式中包含的字符串直接量还可以使用变量来代替:
$greeting = "World";
print "It matches " if "Hello World" =~ /$greeting/;
如果你要匹配的字符串存放在$_
里,则语句中的$_ =~
部分可以省略:
$_ = "Hello World";
print "It matches " if /World/;
另外,正则默认的分隔符//
还可以换成其他任意配对的符号,不过必须要在前面加上'm'
:
"Hello World" =~ m!World!; # 匹配成功,分隔符为'!'
"Hello World" =~ m{World}; # 匹配成功,分隔符为'{}'
"/usr/bin/perl" =~ m"/perl"; # 匹配成功,分隔符为'"',斜杠/不再是分隔符,而是正则中的一个普通字符
正则必须要严格匹配目标字符串中的每个字符才算匹配成功:
"Hello World" =~ /world/; # 不匹配,字符w大小写不同
"Hello World" =~ /o W/; # 匹配成功,空格' '也是一个普通字符
"Hello World" =~ /World /; # 不匹配,字符串末尾没有空格' '
Perl会尽可能早的在字符串最左边发生匹配:
"Hello World" =~ /o/; # 匹配了'Hello'中的'o'
"That hat is red" =~ /hat/; # 匹配了'That'中的'hat'
在匹配目标字符串的过程中,并不是所有的字符都匹配它们本身.有一些字符,称之为元字符,它们属于正则表达式表示法中的保留字符,拥有特殊意义.元字符一共有下面这些:
{}[]()^$.|*+?
想要匹配一个元字符本身,可以使用反斜杠来转义这个元字符:
"2+2=4" =~ /2+2/; # 匹配失败,+是一个元字符
"2+2=4" =~ /2+2/; # 匹配成功,+可以匹配普通的+号
'C:WIN32' =~ /C:\WIN/; # 匹配成功
"/usr/bin/perl" =~ //usr/bin/perl/; # 匹配成功
在最后一个正则中,斜杠'/'
前面也必须使用反斜杠转义,因为斜杠已经用作了正则分隔符.
在正则中,不可打印的ASCII字符可以使用转义序列来表示.常用的例子有:用t
来表示一个制表符,用n
来表示一个换行符,用r
来标识一个回车符.除此之外,任意的字节都可以使用八进制转义序列,比如033
,或者16进制转义序列,比如x1B
来表示:
"1000 2000" =~ m(0 2) # 匹配成功
"cat" =~ /143x61x74/ # 匹配成功,使用了ASCII码转义序列
正则在很多表现上类似于用双引号包围的字符串,比如也可以进行变量内插:
$foo = 'house';
'cathouse' =~ /cat$foo/; #匹配成功
'housecat' =~ /${foo}cat/; #匹配成功
在上面所有的这些例子中,只要正则匹配了目标字符串中的某一部分,就可以算作是匹配成功.如果想要限制匹配具体发生的位置,我们可以使用锚点元字符 ^
和$
.锚点^
匹配的是字符串的开头位置,锚点$
匹配的是字符串的结束位置,或者字符串尾部的换行符之前的位置.下面是一些例子:
"housekeeper" =~ /keeper/; #匹配成功
"housekeeper" =~ /^keeper/; #匹配失败
"housekeeper" =~ /keeper$/; #匹配成功
"housekeeper " =~ /keeper$/; #匹配成功
"housekeeper" =~ /^housekeeper$/; #匹配成功
使用字符类
一个字符类可以匹配一系列可能匹配的字符,而不仅仅是单个字符.字符类使用一个方括号[...]
来表示,一系列可能匹配的字符都放在这个方括号里面.下面是一些例子:
/cat/; # 匹配'cat'
/[bcr]at/; # 匹配'bat','cat',或'rat'
"abc" =~ /[cab]/; # 匹配'a'
在上面最后一条语句中,虽然'c'
是字符类中的第一个字符,但该正则最先匹配到的是目标字符串最左边的字符'a'
.
/[yY][eE][sS]/; # 忽略大小写匹配'yes',即是'yes','Yes','YES',等.
/yes/i; # 效果同上
第二个正则使用了'i'
修饰符,该修饰符的作用是让匹配忽略大小写.
字符类的字符中也有普通字符和特殊字符(元字符)之分,但字符类中的普通字符和特殊字符与字符类外部的不同.字符类中的特殊字符有这些:-]^$
,想要匹配它们本身必须使用反斜杠转义:
/[]c]def/; # 匹配']def'或'cdef'
$x = 'bcr';
/[$x]at/; # 匹配'bat,'cat',或'rat'
/[$x]at/; # 匹配'$at'或'xat'
/[\$x]at/; # 匹配'at','bat,'cat',或'rat'
在字符类中,特殊字符'-'
作为了范围运算符,利用范围运算符,冗长的[0123456789]
和[abc...xyz]
可以精简成[0-9]
和[a-z]
:
/item[0-9]/; # 匹配'item0'或...或'item9'
/[0-9a-fA-F]/; # 匹配一个十六进制数字
但如果'-'
字符放在了字符类的开头处或末尾处,该字符就会被当作一个普通字符来看待.
字符^
只有放在字符类的开头处才算是一个特殊字符,它代表的是否定字符类,效果是让该字符类能够匹配那些所有不在该字符类内部的字符.普通字符类[...]
和否定字符类[^...]
都必须匹配一个有效的字符,不能什么也不匹配.下面是例子:
/[^a]at/; # 不匹配'aat'或'at',但匹配其他所有的类似'bat','cat,'0at','%at',等.
/[^0-9]/; # 匹配一个非数值字符
/[a^]at/; # 匹配'aat'或'^at',这里'^'是个普通字符
Perl支持一些常用字符类的简写形式.(下面讲的这些简写形式的意义都得在该正则开启了ASCII安全模式(使用/a
修饰符)的前提下才正确,否则这些简写形式也能匹配非ASCII的Unicode字符.查看perlrecharclass文档中的反斜杠序列了解详情.)
-
d 代表一个数字字符,相当于:
[0-9]
-
s 代表一个空白符,相当于:
[ f]
-
w 代表一个单词字符(字母,数字,下划线),相当于:
[0-9a-zA-Z_]
-
D 是d的否定形式,它代表任何一个非数字字符,相当于:
[^0-9]
-
S 是s的否定形式,它代表任何一个非空白符字符,相当于:
[^s]
-
W 是w的否定形式,它代表任何一个非单词字符,相当于:
[^w]
-
点号'.'匹配任何一个非换行符" "的字符.相当于:
[^ ]
dswDSW
这些简写形式可以使用在字符类外部,也可以使用在字符类内部.下面是一些例子:
/dd:dd:dd/; # 匹配hh:mm:ss时间格式
/[ds]/; # 匹配任何一个数字或空白符
/wWw/; # 匹配一个单词字符,后跟一个非单词字符,再跟一个单词字符
/..rt/; # 匹配任意两个非换行符的字符,后跟'rt'
/end./; # 匹配'end.'
/end[.]/; # 同上,匹配'end.'
单词锚点b
匹配一个单词边界,也就是一个单词字符和非单词字符wW
或Ww
中间的那个位置,比如:
$x = "Housecat catenates house and cat";
$x =~ /cat/; # 匹配'catenates'中的cat
$x =~ /cat/; # 匹配'housecat'中的cat
$x =~ /cat/; # 匹配字符串结尾处的'cat'
在最后一个例中,字符串结尾处的位置也是一个单词边界.
匹配这个或那个
我们可以使用多选元字符'|'
来匹配多个可能的字符序列.比如想匹配dog
或cat
,我们可以用正则dog|cat
.和前面一样,Perl会从目标字符串的最左边开始进行匹配.在每个字符处,Perl会首先尝试匹配第一个选项dog
.如果dog
不匹配,再尝试下一个选项cat
.如果cat
也不匹配,则匹配失败,Perl会开始尝试目标字符串中的下个字符.下面是一些例子:
"cats and dogs" =~ /cat|dog|bird/; # 匹配"cat"
"cats and dogs" =~ /dog|cat|bird/; # 匹配"cat"
在第二个正则中,即使dog
是第一个选项,但cat
还是最先匹配了字符串最左边的cats
.
"cats" =~ /c|ca|cat|cats/; # 匹配"c"
"cats" =~ /cats|cat|ca|c/; # 匹配"cats"
在目标字符串中的某个字符位置处,会优先匹配正则多选分支中的第一个选项,如果匹配成功,则继续匹配下面的正则,如果匹配失败,才尝试下个选项.
分组和层次匹配
分组元字符()
可以让正则中的一部分内容被当作一个单元来对待.比如正则house(cat|keeper)
会匹配一个house
后跟一个cat
或keeper
.下面有更多的例子:
/(a|b)b/; # 匹配'ab'或'bb'
/(^a|b)c/; # 匹配字符串开头的'ac'或者任意位置的'bc'
/house(cat|)/; # 匹配'housecat'或'house'
/house(cat(s|)|)/; # 匹配'housecats'或'housecat'或'house'.分组也可以嵌套.
"20" =~ /(19|20|)dd/; # 匹配了最后一个空选项'()dd',因为'20dd'不匹配
提取匹配内容
我们还可以利用分组元字符()
提取出目标字符串中自己感兴趣的那部分内容.每个分组匹配的内容都会被存储到一个特殊变量$1
,$2
,等中.可以像普通变量一样使用它们:
# 提取出时,分,秒
$time =~ /(dd):(dd):(dd)/; # 匹配hh:mm:ss格式
$hours = $1;
$minutes = $2;
$seconds = $3;
在列表上下文中,正则的匹配操作会返回一个包含了所有分组对应的匹配变量的列表($1,$2,...)
.所以我们可以把上面的代码改写为:
($hours, $minutes, $second) = ($time =~ /(dd):(dd):(dd)/);
如果正则中存在嵌套分组,则$1
对应了最左边的那个左括号所属的分组, $2
则对应了第二个左括号所在的分组,等等.也就是说,无论分组有没有嵌套,一个分组的序号是几只看该分组左括号的位置.例如,下面是个比较复杂的正则,分组序号已经标示了出来:
/(ab(cd|ef)((gi)|j))/;
1 2 34
还有一种和匹配变量$1
, $2
, ...相关联的值,称之为反向引用g1
, g2
, ... 反向引用是用在正则内部的匹配变量:
/(www)sg1/; # 寻找字符串中类似'the the'的字符序列
需要注意的是:$1
, $2
, ... 应该只在正则外部使用,g1
, g2
, ...应该只在正则内部使用.
重复匹配
可以使用量词元字符?
, *
, +
,和{}
来重复正则中的某一部分内容.量词必须紧跟在一个字符,字符类,或者分组的后面.它们各自的意义如下:
-
a?
= 匹配'a'1次或0次 -
a*
= 匹配'a'0次或更多次,也就是任意次 -
a+
= 匹配'a'1次或更多次,也就是至少一次 -
a{n,m}
= 匹配'a'至少n
次,最多m
次. -
a{n,}
= 匹配'a'至少n
次 -
a{n}
= 匹配'a'n
次,不多不少.
下面是一些例子:
/[a-z]+s+d*/; # 匹配一个至少包含一个字符的单词,后跟一个至少包含一个字符的连续空白符,再跟一个任意长度甚至是0位的数字
/(w+)s+g1/; # 匹配两个任意长度的重复单词
$year =~ /^d{2,4}$/; # 确保变量year的值是一个2到4位的数字
$year =~ /^d{4}$|^d{2}$/; # 这个更准确一点,因为年份不需要3位数字
这些量词总会尽可能多的匹配字符串中的字符.例如:
$x = 'the cat in the hat';
$x =~ /^(.*)(at)(.*)$/; # 匹配完成后,
# $1 = 'the cat in the h'
# $2 = 'at'
# $3 = '' (空匹配)
第一个.*
一直匹配到了最后一个at的左边,第二个.*
没有字符可以匹配了,所以它匹配了0次.
更多匹配
还有一些关于匹配运算符的东西你需要知道.那就是全局修饰符//g
可以让匹配运算符在同一个字符串上匹配多次.在标量上下文中,如果该正则拥有//g
修饰符,则在第一次匹配成功后,下次匹配会从目标字符串中上次成功匹配的结束索引位置开始匹配.而且你还可以使用pos()
函数读取或修改目标字符串中上次成功匹配的索引位置.比如,下面的例子:
$x = "cat dog house"; # 3个单词
while ($x =~ /(w+)/g) {
print "Word is $1, ends at position ", pos $x, " ";
}
打印出:
Word is cat, ends at position 3
Word is dog, ends at position 7
Word is house, ends at position 13
一次失败的匹配或者改变目标字符串会重置当前索引位置.如果你不想在匹配失败后重置当前位置,可以使用//c
修饰符,也就成了/regex/gc
.
在列表上下文中,//g
会返回所有的匹配分组,如果正则中没有分组,则返回整个正则的匹配结果组成的列表,比如:
@words = ($x =~ /(w+)/g); # matches,
# $word[0] = 'cat'
# $word[1] = 'dog'
# $word[2] = 'house'
搜索和替换
在Perl中,字符串的搜索和替换使用s/regex/replacement/modifiers
这样的形式.其中replacement
是一个双引字符串,目标字符串中所有被regex
匹配的子字符串都会被replacement
代替.这里用来关联目标字符串和s///
的运算符仍然是=~
,如果需要匹配的目标字符串存储在$_
中,那么$_ =~
可以省略掉.如果匹配成功,s///
运算符会返回总共替换的次数,否则返回假.下面是一些例子:
$x = "Time to feed the cat!";
$x =~ s/cat/hacker/; # $x的值变成了"Time to feed the hacker!"
$y = "'quoted words'";
$y =~ s/^'(.*)'$/$1/; # 删除掉单引号,$y的值成了"quoted words"
使用s///
运算符,匹配变量$1
,$2
,等可以直接用在replacement
中.如果正则拥有全局修饰符,s///g
会搜索并替换掉源字符串中所有的匹配项:
$x = "I batted 4 for 4";
$x =~ s/4/four/; # $x的值成了"I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g; # $x的值成了"I batted four for four"
无损修饰符s///r
会让替换的结果作为返回值返回而不会修改原变量中的值:
$x = "I like dogs.";
$y = $x =~ s/dogs/cats/r;
print "$x $y "; # 打印出"I like dogs. I like cats."
$x = "Cats are great.";
print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~ s/Frogs/Hedgehogs/r, " ";
#打印出"Hedgehogs are great."
@foo = map { s/[a-z]/X/r } qw(a b c 1 2 3);
# @foo现在的值为qw(X X X 1 2 3)
求值修饰符s///e
会将替换字符串隐式的传入eval{...}
来求值,返回的结果才作为最终的替换字符串.下面是一些例子:
# 将字符串中所有的单词都反向拼写
$x = "the cat in the hat";
$x =~ s/(w+)/reverse $1/ge; # $x的值成了"eht tac ni eht tah"
# 将百分号转换成十进制数
$x = "A 39% hit rate";
$x =~ s!(d+)%!$1/100!e; # $x的值成了"A 0.39 hit rate"
上面的最后一个例子演示了s///
运算符还可以使用其他的分隔符,比如s!!!
和s{}{}
,甚至s{}//
.如果使用了单引号s'''
,则正则和替换字符串都会被当作单引字符串,也就不能进行变量内插.
split运算符
split /regex/, string
可以将string
分割到一个子字符串列表中,然后返回这个列表.参数中的正则表达式可以指定应该拿什么字符序列来分割源字符串string
.例如,想将一个字符串分割成多个单个的单词,使用:
$x = "Calvin and Hobbes";
@word = split /s+/, $x; # $word[0] = 'Calvin'
# $word[1] = 'and'
# $word[2] = 'Hobbes'
分割一个由逗号分割的数字列表,使用:
$x = "1.618,2.718, 3.142";
@const = split /,s*/, $x; # $const[0] = '1.618'
# $const[1] = '2.718'
# $const[2] = '3.142'
如果分割符使用了空正则//
,则源字符串会被分割为一个个的字符.如果正则中包含分组,那么返回的结果列表中还会包含分组中匹配的内容:
$x = "/usr/bin";
@parts = split m!(/)!, $x; # $parts[0] = ''
# $parts[1] = '/'
# $parts[2] = 'usr'
# $parts[3] = '/'
# $parts[4] = 'bin'
因为$x的第一个字符就匹配了分隔符中指定的正则,所以split
返回的列表中的第一个元素是个空字符串.
BUGS
无.
相关链接
这仅仅是一个快速入门指南,想要一份更深入的正则表达式教程,请参阅perlretut,以及完整的参考perlre.
作者和版权
Copyright (c) 2000 Mark Kvale All rights reserved.
This document may be distributed under the same terms as Perl itself.
致谢
笔者在此要感谢Mark-Jason Dominus, Tom Christiansen, Ilya Zakharevich, Brad Hughes, 以及Mike Giroux,感谢他们提出了宝贵的意见.