第六章 正则表达式

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;
#匹配时,执行代码并以返回值替换
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容