perlrequick中文版

目录

名称

perlrequick - Perl的正则表达式快速入门

描述

本页面能够让你了解并使用Perl中最基本的正则表达式用法.

指南

简单的单词匹配

最简单的正则就是一个单词,或者更通用一点,一个字符序列.一个仅包含某个单词的正则可以匹配包含那个单词的任意字符串:

"Hello World" =~ /World/; # 匹配成功

在这条语句中,World就是一个正则,/World/两边的双斜杠//告诉Perl这是一个正则表达式.运算符=~会把任意的字符串传递给该正则表达式,如果正则匹配成功,则返回真,如果匹配失败,则返回假.在上面的例子中,World刚好匹配了字符串"Hello World"中的第二个单词,所以该表达式返回的值为真.

类似的这种表达式经常会被用在条件判断语句中:

print "It matches\n" if "Hello World" =~ /World/;

还可以使用!~运算符来表示相反的意思,也就是"不匹配":

print "It doesn't match\n" if "Hello World" !~ /World/;

正则表达式中包含的字符串直接量还可以使用变量来代替:

$greeting = "World";
print "It matches\n" if "Hello World" =~ /$greeting/;

如果你要匹配的字符串存放在$_里,则语句中的$_ =~部分可以省略:

$_ = "Hello World";
print "It matches\n" 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\t2000" =~ m(0\t2)      # 匹配成功
"cat" =~ /\143\x61\x74/ # 匹配成功,使用了ASCII码转义序列

正则在很多表现上类似于用双引号包围的字符串,比如也可以进行变量内插:

$foo = 'house';
'cathouse' =~ /cat$foo/; #匹配成功
'housecat' =~ /${foo}cat/; #匹配成功

在上面所有的这些例子中,只要正则匹配了目标字符串中的某一部分,就可以算作是匹配成功.如果想要限制匹配具体发生的位置,我们可以使用锚点元字符 ^$.锚点^匹配的是字符串的开头位置,锚点$匹配的是字符串的结束位置,或者字符串尾部的换行符之前的位置.下面是一些例子:

"housekeeper" =~ /keeper/;         #匹配成功
"housekeeper" =~ /^keeper/; #匹配失败
"housekeeper" =~ /keeper$/; #匹配成功
"housekeeper\n" =~ /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 代表一个空白符,相当于:

    [\ \t\r\n\f]
  • \w 代表一个单词字符(字母,数字,下划线),相当于:

    [0-9a-zA-Z_]
  • \D 是\d的否定形式,它代表任何一个非数字字符,相当于:

    [^0-9]
  • \S 是\s的否定形式,它代表任何一个非空白符字符,相当于:

    [^\s]
  • \W 是\w的否定形式,它代表任何一个非单词字符,相当于:

    [^\w]
  • 点号'.'匹配任何一个非换行符"\n"的字符.相当于:

    [^\n]

 \d\s\w\D\S\W这些简写形式可以使用在字符类外部,也可以使用在字符类内部.下面是一些例子:

/\d\d:\d\d:\d\d/; # 匹配hh:mm:ss时间格式
/[\d\s]/; # 匹配任何一个数字或空白符
/\w\W\w/; # 匹配一个单词字符,后跟一个非单词字符,再跟一个单词字符
/..rt/; # 匹配任意两个非换行符的字符,后跟'rt'
/end\./; # 匹配'end.'
/end[.]/; # 同上,匹配'end.'

单词锚点\b匹配一个单词边界,也就是一个单词字符和非单词字符\w\W\W\w中间的那个位置,比如:

$x = "Housecat catenates house and cat";
$x =~ /\bcat/; # 匹配'catenates'中的cat
$x =~ /cat\b/; # 匹配'housecat'中的cat
$x =~ /\bcat\b/; # 匹配字符串结尾处的'cat'

在最后一个例中,字符串结尾处的位置也是一个单词边界.

匹配这个或那个

我们可以使用多选元字符'|'来匹配多个可能的字符序列.比如想匹配dogcat,我们可以用正则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后跟一个catkeeper.下面有更多的例子:

/(a|b)b/;                # 匹配'ab'或'bb'
/(^a|b)c/; # 匹配字符串开头的'ac'或者任意位置的'bc'
/house(cat|)/; # 匹配'housecat'或'house'
/house(cat(s|)|)/; # 匹配'housecats'或'housecat'或'house'.分组也可以嵌套.
"20" =~ /(19|20|)\d\d/; # 匹配了最后一个空选项'()\d\d',因为'20\d\d'不匹配

提取匹配内容

我们还可以利用分组元字符()提取出目标字符串中自己感兴趣的那部分内容.每个分组匹配的内容都会被存储到一个特殊变量$1,$2,等中.可以像普通变量一样使用它们:

# 提取出时,分,秒
$time =~ /(\d\d):(\d\d):(\d\d)/; # 匹配hh:mm:ss格式
$hours = $1;
$minutes = $2;
$seconds = $3;

在列表上下文中,正则的匹配操作会返回一个包含了所有分组对应的匹配变量的列表($1,$2,...).所以我们可以把上面的代码改写为:

($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/);

如果正则中存在嵌套分组,则$1对应了最左边的那个左括号所属的分组, $2则对应了第二个左括号所在的分组,等等.也就是说,无论分组有没有嵌套,一个分组的序号是几只看该分组左括号的位置.例如,下面是个比较复杂的正则,分组序号已经标示了出来:

/(ab(cd|ef)((gi)|j))/;
1 2 34

还有一种和匹配变量$1 , $2 , ...相关联的值,称之为反向引用\g1 , \g2 , ... 反向引用是用在正则内部的匹配变量:

/(\w\w\w)\s\g1/; # 寻找字符串中类似'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, "\n";
}

打印出:

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\n"; # 打印出"I like dogs. I like cats."

$x = "Cats are great.";
print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~ s/Frogs/Hedgehogs/r, "\n";
#打印出"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,感谢他们提出了宝贵的意见.

posted @ 2013-01-06 15:33  紫云飞  阅读(1827)  评论(0编辑  收藏  举报