Perl大部分的文本处理能力来自它所使用的正则表达式。
所谓正则表达式就是一个模式 ,这个模式描述了一段文本所具有的特征。正则表达式引擎就应用这些模式来匹配和替换文本。
Perl中相关的正则表达式文档有:
perldoc perlretut 入门教程
perldoc perlreref 参考指南
perldoc perlre 有关正则的完整文档
字面量
一个正则表达式可以简单到就是一些字符:
my $name = 'Chatfield';
say 'Found a hat!' if $name =~ /hat/;
匹配操作符(m//, 简写为 //)标识着这是一个正则表达式。
这个正则表达式表示了这样的一个模式特征:字符h 后面跟着一个字符a,再后面是个字符t。
正则表达式的绑定操作符(=~ )是一个中缀操作符,前面是要测试的字符串,后面是正则表达式(模式)。
在标量语境中,若匹配则返回真值,不匹配就返回假。绑定操作符的否定形式(!~)求值规则相反:匹配返回假值,不匹配返回真值。
替换操作符(s///),某种意义上来说是个环缀操作符,它有2个操作数,第一个操作数是正则表达式,第二个操作数是将匹配的部分替换成什么。使用绑定操作符来绑定操作对象。
my $status = 'I feel ill.';
$status =~ s/ill/well/;
say $status;
#I feel well.
qr//操作符和正则表达式组合
qr//操作符会创建一个正则表达式,这样就能在使用正则表达式的地方直接使用:
my $hat = qr/hat/;
say 'Found a hat!' if $name =~ /$hat/;
#组合起来使用:
my $hat = qr/hat/;
my $field = qr/field/;
say 'Found a hat in a field!'
if $name =~ /$hat$field/;
like( $name, qr/$hat$field/, 'Found a hat in a field!' );
#Test::More 的like()函数,第一个参数是要测试的目标,第二个参数是正则表达式模式。
量词
使用量词可以使正则表达式威力大增。量词用来表示字符出现的频率。
?表示0次或1次:
my $cat_or_ct = qr/ca?t/;
#ct匹配,cat匹配,caat不匹配
like( 'cat', $cat_or_ct, "'cat' matches /ca?t/" );
like( 'ct', $cat_or_ct, "'ct' matches /ca?t/" );
+表示一次或多次(也就是至少一次):
my $some_a = qr/ca+t/;
like( 'cat', $some_a, "'cat' matches /ca+t/" );
like( 'caat', $some_a, "'caat' matches/" );
like( 'caaat', $some_a, "'caaat' matches" );
like( 'caaaat', $some_a, "'caaaat' matches" );
unlike( 'ct', $some_a, "'ct' does not match" );
*表示任意次,0次、1次、多次均匹配:
my $any_a = qr/ca*t/;
like( 'cat', $any_a, "'cat' matches /ca*t/" );
like( 'caat', $any_a, "'caat' matches" );
like( 'caaat', $any_a, "'caaat' matches" );
like( 'caaaat', $any_a, "'caaaat' matches" );
like( 'ct', $any_a, "'ct' matches" );
#量词*的使用需要留心,容易出错。
指定明确次数n次,{n}
# equivalent to qr/cat/;
my $only_one_a = qr/ca{1}t/;
like( 'cat', $only_one_a, "'cat' matches /ca{1}t/" );
{n,}至少n次
# equivalent to qr/ca+t/;
my $some_a = qr/ca{1,}t/;
like( 'cat', $some_a, "'cat' matches /ca{1,}t/" );
like( 'caat', $some_a, "'caat' matches" );
like( 'caaat', $some_a, "'caaat' matches" );
like( 'caaaat', $some_a, "'caaaat' matches" );
{n,m}不少于n次,不大于m次
my $few_a = qr/ca{1,3}t/;
like( 'cat', $few_a, "'cat' matches /ca{1,3}t/" );
like( 'caat', $few_a, "'caat' matches" );
like( 'caaat', $few_a, "'caaat' matches" );
unlike( 'caaaat', $few_a, "'caaaat' doesn't match" );
贪婪
量词+ 和*具有贪婪的特性,默认它们会尽可能多去匹配。
# a poor regex
my $hot_meal = qr/hot.*meal/;
say 'Found a hot meal!' if 'I have a hot meal' =~ $hot_meal;
say 'Found a hot meal!' if 'one-shot, piecemeal work!' =~ $hot_meal;
使用?来转化为非贪婪的,非贪婪量词会尽量短的匹配。
my $minimal_greedy = qr/hot.*?meal/;
锚位
锚位就是做位置上的控制。
字符串开始(\A)
# also matches "lammed", "lawmaker", and "layman"
my $seven_down = qr/\Al${letters_only}{2}m/;
end of line string anchor (\z)
# also matches "loom", but an obvious improvement
my $seven_down = qr/\Al${letters_only}{2}m\z/;
你还可能见过用^ 和 $用来表示字符串开始和结束位置。^其实是匹配换行符后面,同样的$匹配的是换行符前面。\A 和\z更具体,建议使用这种方式。
字符边界(\b),匹配的是字符(\w)和非字符(\W)之间的那个位置。边界不是一个字符,它0宽度,不可见。
my $seven_down = qr/\bl${letters_only}{2}m\b/;
元字符
正则表达式中,元字符代表的不是字面本身的意思,而是解释出来的意思。我们已经见过元字符了,比如斜杠(\)和量词(?)。
一些元字符:
. 代表换行符之外的一切东西
\w 代表所有数字,字符和下划线。
\d 代表所有数字
\s 代表空白:空格,制表符,回车,换页,换行符
一般使用大写来表示相反的意思。
\ W匹配任何字符,除了数字,字符和下划线
\ D匹配一个非数字字符
\ S匹配任何东西,但空白除外
\ B匹配任何位置除了字符边界
字符集
你可以使用方括号来组织需要的候选项:
my $ascii_vowels = qr/[aeiou]/;
my $maybe_cat = qr/c${ascii_vowels}t/;
方括号中还允许使用连字符(-)来包含连续的范围:
my $ascii_letters_only = qr/[a-zA-Z]/;
如果要将连字符作为候选的成员,可以将它放在最前面或最后面或进行转义:
my $interesting_punctuation = qr/[-!?]/;
my $line_characters = qr/[|=\-_]/;
在最前面放置^表示取反:
my $not_an_ascii_vowel = qr/[^aeiou]/;
#如果想要把它是作为候选的成员,那就放在其他位置,或者直接转义它。
捕获
正则表达式允许你使用括号来捕获匹配的部分,以备过后使用。
#假设电话号码为:(202)456-1111
my $area_code = qr/\(\d{3}\)/;
my $local_number = qr/\d{3}-?\d{4}/;
my $phone_number = qr/$area_code\s?$local_number/;
#注意第一行的括号需要转义,因为需要原样的放到更复杂的正则表达式中。
以左括号的次序,依次将匹配的结果存入变量$1,$2,$3....
#捕获
if ($contact_info =~ /($phone_number)/)
{
say "Found a number $1";
}
在列表语境中,返回的是捕获结果。
给捕获命名
我们可以给要捕获的组增加名字来更好的识别它:
if ($contact_info =~ /(?<phone>$phone_number)/)
{
say "Found a number $+{phone}";
}
命名捕获的语法:
(?<capture name> ... )
匹配成功时,以名字为键,匹配的部分为值存入到“%+”这个哈希中。
分组
my $pork = qr/pork/;
my $beans = qr/beans/;
like( 'pork and beans', qr/\A$pork?.*?$beans/, 'maybe pork, definitely beans' );
如果你自己手动展开这个正则表达式,结果可能不是你期望的:
my $pork_and_beans = qr/\Apork?.*beans/;
like( 'pork and beans', qr/$pork_and_beans/, 'maybe pork, definitely beans' );
like( 'por and beans', qr/$pork_and_beans/, 'wait... no phylloquinone here!' );
#均会匹配
来我们把模式写得更精确些:
my $pork = qr/pork/;
my $and = qr/and/;
my $beans = qr/beans/;
like( 'pork and beans', qr/\A$pork? $and? $beans/, 'maybe pork, maybe and, definitely beans' );
可选操作符(|):
my $rice = qr/rice/;
my $beans = qr/beans/;
like( 'rice', qr/$rice|$beans/, 'Found rice' );
like( 'beans', qr/$rice|$beans/, 'Found beans' );
这样写容易产生疑惑:
like( 'rice', qr/rice|beans/, 'Found rice' );
like( 'beans', qr/rice|beans/, 'Found beans' );
unlike( 'ricb', qr/rice|beans/, 'Found hybrid' );
但是实际上由于| 的操作符优先级很低,意思是一样的,不过还是使用变量比较清楚。
括号默认会启用捕获,但是可以指定为不捕获。
my $starches = qr/(?:pasta|potatoes|rice)/;
#不捕获,括号只是用来分组。
其他转义序列
对于元字符,要匹配它们的字面意思就需要转义。
匹配左括号 \(
匹配字符点 \.
这些字符也要转义才能表示它们的字面意思 |,$,+,?,*
还有一个方法就是使用元字符禁用字符(\Q \E)来禁用元字符特性。当模式来源于外部(你无法控制)时,这个特性非常有用:
my ($text, $literal_text) = @_;
return $text =~ /\Q$literal_text\E/;
#此时$literal_text里面的字符将没有元字符特性,任何字符都是字面意义。
#比如是字符串 .*A就等同\.\*A
#表示一个点号一个*号一个A。
处理用户输入的模式时要小心,一个恶意的正则表达式可能需要花费超常时间来匹配,从而导致拒绝服务攻击。
断言
正则表达式中的锚位如\A \b \B \Z就是断言,断言的作用是要求字符串符合一定的条件,本身并不匹配任何字符(所以又称零宽断言)。无论字符串的内容是什么,正则表达式qr/\A/总是会匹配成功。
这是一个很重要的特性:零宽断言不会消费字符串,它只是一种要求模式。比如你想要找到单词cat,就可以用边界断言(锚位):
my $just_a_cat = qr/cat\b/;
你也可以要求cat后面不能是字符串astrophe,这时就可以使用零宽否定前向断言(?!...)语法:
my $safe_feline = qr/cat(?!astrophe)/;
对应的还有零宽肯定前向断言(?=...):
my $disastrous_feline = qr/cat(?=astrophe)/;
#cat后面必须是字符串astrophe才能匹配。
还有向后的零宽断言,零宽否定后向断言(?<!...):
my $middle_cat = qr/(?<!\A)cat/;
#不能一开始就是cat
零宽肯定后向断言(?<=...):
my $space_cat = qr/(?<=\s)cat/;
#cat前面必须是空白字符
Perl 正则表达式还有个功能:保持断言(\K),它的作用是用来保持匹配的状态,但是\K左边的字符不会被放在匹配中的部分。(其实相当于一个后向断言)
有些情况下非常有用:
#替换一部分
my $exclamation = 'This is a catastrophe!';
$exclamation =~ s/cat\K\w+!/./;
#只会替换\K后面的那部分。
like( $exclamation, qr/\bcat\./, "That wasn't so bad!" );
正则修饰符
修饰符会改变正则表达式的行为,通常是在模式末尾使用。
禁用大小写敏感:
my $pet = 'CaMeLiA';
like( $pet, qr/Camelia/, 'Nice butterfly!' );
like( $pet, qr/Camelia/i, 'shift key br0ken' );
也可以集成在模式中:
my $some='cat';
my $find_a_cat = qr/(?<feline>cat)/;
print $+{feline} if $some=~/$find_a_cat/;
#(?i)语法就表示忽略大小写
#禁用指定的修饰符可以在前面使用减号,如(?-i)就表示必须大小写一致才会匹配。
其他的修饰符:
/m 启用多行模式,^和$匹配字符串内的换行符,而非字符串的开头和末尾
/s 点号可以匹配任何字符,包括换行符
/r 非破坏方式替换字符
my $status = 'I am hungry for pie.';
my $newstatus = $status =~ s/pie/cake/r;
my $statuscopy = $status =~ s/liver and onions/bratwurst/r;
is( $status, 'I am hungry for pie.', 'original string should be unmodified' );
like( $newstatus, qr/cake/, 'cake wanted' );
unlike( $statuscopy, qr/bratwurst/, 'wurst not' );
/x 可以在模式中增加空白和注释,增加可读性
my $attr_re = qr{
\A # start of line
(?:
[;\n\s]* # spaces and semicolons
(?:/\*.*?\*/)? # C comments
)*
ATTR
\s+
( U?INTVAL
| FLOATVAL
| STRING\s+\*
)
}x;
/g 全局替换
# appease the Mitchell estate
my $contents = slurp( $file );
$contents =~ s/Scarlett O'Hara/Mauve Midway/g;
\G 在上一个匹配处进行匹配
while ($contents =~ /\G(\w{3})(\w{3})(\w{4})/g)
{
push @numbers, "($1) $2-$3";
}
/e 修饰符让你具有更灵活的替换字符串能力,由代码运行的返回值作为替代的字符串
$sequel =~ s{Scarlett( O'Hara)?}
{
'Mauve' . defined $1
? ' Midway'
: ''
}ge;
#匹配时,执行代码并以返回值替换