Perl大部分的文本處理能力來(lái)自它所使用的正則表達(dá)式悠汽。
所謂正則表達(dá)式就是一個(gè)模式 渊啰,這個(gè)模式描述了一段文本所具有的特征蜡豹。正則表達(dá)式引擎就應(yīng)用這些模式來(lái)匹配和替換文本销凑。
Perl中相關(guān)的正則表達(dá)式文檔有:
perldoc perlretut 入門(mén)教程
perldoc perlreref 參考指南
perldoc perlre 有關(guān)正則的完整文檔
字面量
一個(gè)正則表達(dá)式可以簡(jiǎn)單到就是一些字符:
my $name = 'Chatfield';
say 'Found a hat!' if $name =~ /hat/;
匹配操作符(m//, 簡(jiǎn)寫(xiě)為 //)標(biāo)識(shí)著這是一個(gè)正則表達(dá)式。
這個(gè)正則表達(dá)式表示了這樣的一個(gè)模式特征:字符h 后面跟著一個(gè)字符a渺绒,再后面是個(gè)字符t贺喝。
正則表達(dá)式的綁定操作符(=~ )是一個(gè)中綴操作符,前面是要測(cè)試的字符串宗兼,后面是正則表達(dá)式(模式)躏鱼。
在標(biāo)量語(yǔ)境中,若匹配則返回真值殷绍,不匹配就返回假染苛。綁定操作符的否定形式(!~)求值規(guī)則相反:匹配返回假值,不匹配返回真值主到。
替換操作符(s///)茶行,某種意義上來(lái)說(shuō)是個(gè)環(huán)綴操作符,它有2個(gè)操作數(shù)登钥,第一個(gè)操作數(shù)是正則表達(dá)式畔师,第二個(gè)操作數(shù)是將匹配的部分替換成什么。使用綁定操作符來(lái)綁定操作對(duì)象牧牢。
my $status = 'I feel ill.';
$status =~ s/ill/well/;
say $status;
#I feel well.
qr//操作符和正則表達(dá)式組合
qr//操作符會(huì)創(chuàng)建一個(gè)正則表達(dá)式看锉,這樣就能在使用正則表達(dá)式的地方直接使用:
my $hat = qr/hat/;
say 'Found a hat!' if $name =~ /$hat/;
#組合起來(lái)使用:
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()函數(shù)姿锭,第一個(gè)參數(shù)是要測(cè)試的目標(biāo),第二個(gè)參數(shù)是正則表達(dá)式模式伯铣。
量詞
使用量詞可以使正則表達(dá)式威力大增呻此。量詞用來(lái)表示字符出現(xiàn)的頻率。
?表示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" );
#量詞*的使用需要留心,容易出錯(cuò)犀斋。
指定明確次數(shù)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" );
貪婪
量詞+ 和*具有貪婪的特性叽粹,默認(rèn)它們會(huì)盡可能多去匹配览效。
# 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;
使用?來(lái)轉(zhuǎn)化為非貪婪的,非貪婪量詞會(huì)盡量短的匹配虫几。
my $minimal_greedy = qr/hot.*?meal/;
錨位
錨位就是做位置上的控制锤灿。
字符串開(kāi)始(\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/;
你還可能見(jiàn)過(guò)用^ 和 $用來(lái)表示字符串開(kāi)始和結(jié)束位置。^其實(shí)是匹配換行符后面辆脸,同樣的$匹配的是換行符前面但校。\A 和\z更具體,建議使用這種方式啡氢。
字符邊界(\b)状囱,匹配的是字符(\w)和非字符(\W)之間的那個(gè)位置。邊界不是一個(gè)字符倘是,它0寬度亭枷,不可見(jiàn)。
my $seven_down = qr/\bl${letters_only}{2}m\b/;
元字符
正則表達(dá)式中搀崭,元字符代表的不是字面本身的意思叨粘,而是解釋出來(lái)的意思。我們已經(jīng)見(jiàn)過(guò)元字符了瘤睹,比如斜杠(\)和量詞(?)升敲。
一些元字符:
. 代表?yè)Q行符之外的一切東西
\w 代表所有數(shù)字,字符和下劃線轰传。
\d 代表所有數(shù)字
\s 代表空白:空格驴党,制表符,回車(chē)绸吸,換頁(yè)鼻弧,換行符
一般使用大寫(xiě)來(lái)表示相反的意思。
\ W匹配任何字符锦茁,除了數(shù)字攘轩,字符和下劃線
\ D匹配一個(gè)非數(shù)字字符
\ S匹配任何東西,但空白除外
\ B匹配任何位置除了字符邊界
字符集
你可以使用方括號(hào)來(lái)組織需要的候選項(xiàng):
my $ascii_vowels = qr/[aeiou]/;
my $maybe_cat = qr/c${ascii_vowels}t/;
方括號(hào)中還允許使用連字符(-)來(lái)包含連續(xù)的范圍:
my $ascii_letters_only = qr/[a-zA-Z]/;
如果要將連字符作為候選的成員码俩,可以將它放在最前面或最后面或進(jìn)行轉(zhuǎn)義:
my $interesting_punctuation = qr/[-!?]/;
my $line_characters = qr/[|=\-_]/;
在最前面放置^表示取反:
my $not_an_ascii_vowel = qr/[^aeiou]/;
#如果想要把它是作為候選的成員度帮,那就放在其他位置,或者直接轉(zhuǎn)義它稿存。
捕獲
正則表達(dá)式允許你使用括號(hào)來(lái)捕獲匹配的部分笨篷,以備過(guò)后使用。
#假設(shè)電話號(hào)碼為:(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/;
#注意第一行的括號(hào)需要轉(zhuǎn)義瓣履,因?yàn)樾枰瓨拥姆诺礁鼜?fù)雜的正則表達(dá)式中率翅。
以左括號(hào)的次序,依次將匹配的結(jié)果存入變量$1袖迎,$2冕臭,$3....
#捕獲
if ($contact_info =~ /($phone_number)/)
{
say "Found a number $1";
}
在列表語(yǔ)境中,返回的是捕獲結(jié)果燕锥。
給捕獲命名
我們可以給要捕獲的組增加名字來(lái)更好的識(shí)別它:
if ($contact_info =~ /(?<phone>$phone_number)/)
{
say "Found a number $+{phone}";
}
命名捕獲的語(yǔ)法:
(?<capture name> ... )
匹配成功時(shí)辜贵,以名字為鍵,匹配的部分為值存入到“%+”這個(gè)哈希中归形。
分組
my $pork = qr/pork/;
my $beans = qr/beans/;
like( 'pork and beans', qr/\A$pork?.*?$beans/, 'maybe pork, definitely beans' );
如果你自己手動(dòng)展開(kāi)這個(gè)正則表達(dá)式托慨,結(jié)果可能不是你期望的:
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!' );
#均會(huì)匹配
來(lái)我們把模式寫(xiě)得更精確些:
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' );
這樣寫(xiě)容易產(chǎn)生疑惑:
like( 'rice', qr/rice|beans/, 'Found rice' );
like( 'beans', qr/rice|beans/, 'Found beans' );
unlike( 'ricb', qr/rice|beans/, 'Found hybrid' );
但是實(shí)際上由于| 的操作符優(yōu)先級(jí)很低,意思是一樣的暇榴,不過(guò)還是使用變量比較清楚厚棵。
括號(hào)默認(rèn)會(huì)啟用捕獲,但是可以指定為不捕獲跺撼。
my $starches = qr/(?:pasta|potatoes|rice)/;
#不捕獲窟感,括號(hào)只是用來(lái)分組。
其他轉(zhuǎn)義序列
對(duì)于元字符歉井,要匹配它們的字面意思就需要轉(zhuǎn)義柿祈。
匹配左括號(hào) \(
匹配字符點(diǎn) \.
這些字符也要轉(zhuǎn)義才能表示它們的字面意思 |,$哩至,+躏嚎,?,*
還有一個(gè)方法就是使用元字符禁用字符(\Q \E)來(lái)禁用元字符特性菩貌。當(dāng)模式來(lái)源于外部(你無(wú)法控制)時(shí)卢佣,這個(gè)特性非常有用:
my ($text, $literal_text) = @_;
return $text =~ /\Q$literal_text\E/;
#此時(shí)$literal_text里面的字符將沒(méi)有元字符特性,任何字符都是字面意義箭阶。
#比如是字符串 .*A就等同\.\*A
#表示一個(gè)點(diǎn)號(hào)一個(gè)*號(hào)一個(gè)A虚茶。
處理用戶輸入的模式時(shí)要小心戈鲁,一個(gè)惡意的正則表達(dá)式可能需要花費(fèi)超常時(shí)間來(lái)匹配,從而導(dǎo)致拒絕服務(wù)攻擊嘹叫。
斷言
正則表達(dá)式中的錨位如\A \b \B \Z就是斷言婆殿,斷言的作用是要求字符串符合一定的條件,本身并不匹配任何字符(所以又稱零寬斷言)罩扇。無(wú)論字符串的內(nèi)容是什么婆芦,正則表達(dá)式qr/\A/總是會(huì)匹配成功。
這是一個(gè)很重要的特性:零寬斷言不會(huì)消費(fèi)字符串喂饥,它只是一種要求模式消约。比如你想要找到單詞cat,就可以用邊界斷言(錨位):
my $just_a_cat = qr/cat\b/;
你也可以要求cat后面不能是字符串a(chǎn)strophe员帮,這時(shí)就可以使用零寬否定前向斷言(?!...)語(yǔ)法:
my $safe_feline = qr/cat(?!astrophe)/;
對(duì)應(yīng)的還有零寬肯定前向斷言(?=...):
my $disastrous_feline = qr/cat(?=astrophe)/;
#cat后面必須是字符串a(chǎn)strophe才能匹配或粮。
還有向后的零寬斷言,零寬否定后向斷言(?<!...):
my $middle_cat = qr/(?<!\A)cat/;
#不能一開(kāi)始就是cat
零寬肯定后向斷言(?<=...):
my $space_cat = qr/(?<=\s)cat/;
#cat前面必須是空白字符
Perl 正則表達(dá)式還有個(gè)功能:保持?jǐn)嘌裕╘K)集侯,它的作用是用來(lái)保持匹配的狀態(tài)被啼,但是\K左邊的字符不會(huì)被放在匹配中的部分幅狮。(其實(shí)相當(dāng)于一個(gè)后向斷言)
有些情況下非常有用:
#替換一部分
my $exclamation = 'This is a catastrophe!';
$exclamation =~ s/cat\K\w+!/./;
#只會(huì)替換\K后面的那部分潦嘶。
like( $exclamation, qr/\bcat\./, "That wasn't so bad!" );
正則修飾符
修飾符會(huì)改變正則表達(dá)式的行為漠趁,通常是在模式末尾使用椰于。
禁用大小寫(xiě)敏感:
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)語(yǔ)法就表示忽略大小寫(xiě)
#禁用指定的修飾符可以在前面使用減號(hào)蚓耽,如(?-i)就表示必須大小寫(xiě)一致才會(huì)匹配歉闰。
其他的修飾符:
/m 啟用多行模式装悲,^和$匹配字符串內(nèi)的換行符椅您,而非字符串的開(kāi)頭和末尾
/s 點(diǎn)號(hào)可以匹配任何字符贱除,包括換行符
/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 可以在模式中增加空白和注釋?zhuān)黾涌勺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 在上一個(gè)匹配處進(jìn)行匹配
while ($contents =~ /\G(\w{3})(\w{3})(\w{4})/g)
{
push @numbers, "($1) $2-$3";
}
/e 修飾符讓你具有更靈活的替換字符串能力生闲,由代碼運(yùn)行的返回值作為替代的字符串
$sequel =~ s{Scarlett( O'Hara)?}
{
'Mauve' . defined $1
? ' Midway'
: ''
}ge;
#匹配時(shí),執(zhí)行代碼并以返回值替換