Perl正則表達式超詳細教程

前言

想必學習perl的人,對基礎正則表達式都已經熟悉,因此學習perl正則會很輕鬆。這裏我不打算解釋基礎正則的內容,而是直接介紹基礎正則中不具有的但perl支持的功能。關於基礎正則表達式的內容,可參閱基礎正則表達式html

另外,本系列只介紹匹配操做,關於內容替換,由於和學習使用perl正則並沒有多大關係,因此替換相關的將在下一篇文章單獨解釋。java

這裏推薦一個學正則很是好的資料:stackflow上關於各類語言(perl/python/.net/java/ruby等等)的正則的解釋、示例,這裏收集的都是對問題解釋的很是清晰且很是經典的回答。在我學習perl正則的時候,對有些功能實在理解不了(想必你也必定會),就會從這裏找答案,而它,也歷來沒讓我失望:https://stackoverflow.com/questions/22937618/reference-what-does-this-regex-mean/22944075#22944075python

如下是perl正則的man文檔:正則表達式

  • perl正則快速入門:man perlrequick
  • perl正則教程:man perlretut
  • perl正則完整文檔:man perlre

學perl正則必備的一點基本語法

新建一個文件做爲perl腳本文件,在其首行寫上#!/usr/bin/perl,它表示用perl做爲本文件的解釋器。寫入一些perl程序後,再賦予執行權限就能夠執行了,或者直接使用perl命令去調用這個腳本文件,前面的兩個過程均可以省略,這和shell腳本的方式是徹底同樣的,無非是將bash替換爲了perl,想必各位都理解。shell

1.print用來輸出信息,至關於shell中的echo命令,但須要手動輸入換行符"\n"進行換行。安全

例如:ruby

#!/usr/bin/perl

print "hello world\n";      # 注意每一句後面都使用分號結尾

保存後,執行它(假設腳本文件名爲test.pl):bash

$ chmod +x test.pl
$ perl test.pl

2.變量賦值app

perl中的變量能夠不用事先聲明,能夠直接賦值甚至直接引用。注意變量名前面老是須要加上$符號,不管是賦值的時候仍是引用的時候,這和其它語言不太同樣。less

#!/usr/bin/perl

$name="longshuai";
$age=18;
print "$name $age \n";

3.if語句用來判斷,語法格式爲:

if(condition){
    body
}else{
    body
}

例如:

$age = 18;
if($age <= 20){
    print "age less than 20\n";
} else {
    print "age greate than 20\n";
}

4.默認參數變量

在perl中,對於須要參數的函數或表達式,但卻沒有給參數,這是將會使用perl的默認參數變量$_

例如,下面的print原本是須要參數的,可是由於沒有給參數,print將輸出默認的參數變量$_,也就是輸出"abcde"。

$_="abcde";
print ;

perl中使用$_的地方很是多,後文還會出現,不過用到的時候我會解釋。

5.讀取標準輸入

perl中使用一對尖括號格式的<STDIN>來讀取來自非文件的標準輸入,例如來自管道的數據,來自輸入重定向的數據或者來自鍵盤的輸入。須要注意的是,<STDIN>讀取的輸入會自帶換行符,因此print輸出的時候不要加上額外的換行符。

例如,在test.pl文件中寫入以下內容:

#!/usr/bin/perl

$data=<STDIN>;
print "$data";

而後用管道傳遞一行數據給perl程序:

echo "abcdefg" | perl test.pl

只是須要注意,將<STDIN>賦值給變量時,將只能讀取一行(遇到換行符就結束讀取)。例以下面的perl將讀取不了"hijklmn"。

echo -e "abcdefg\nhijklmn" | perl test.pl

若是想要讀取多行標準輸入,就不能將其賦值給變量,而是使用foreach來遍歷各行(此處不介紹其它方式):

foreach $line (<STDIN>){
    print "$line";
}

以上就是foreach的語法:

  • 圓括號中的內容是待遍歷對象,一般是一個列表,好比上面用<STDIN>讀取的多行數據就是一個列表,每一行都是列表中的一個元素;
  • $line稱爲控制變量,foreach在每次迭代過程當中都會選中一個列表中的元素賦值給$line,例如將讀取的每一行都賦值給$line。

能夠省略$line,這時就採用默認的參數變量$_,因此如下兩個表達式是等價的:

foreach (<STDIN>){
    print "$_";
}

foreach $_ (<STDIN>){
    print "$_";
}

6.讀取文件中的數據

正則強大的用處就是處理文本數據,因此必需要說明perl如何讀取文件數據來作正則匹配。

咱們能夠將文件做爲perl命令行的參數,perl會使用<>去讀取這些文件中的內容。

foreach (<>){
    print "$_";
}

執行的時候,只要把文件做爲perl命令或腳本文件的參數便可:

perl test.pl /etc/passwd

7.去掉行尾分隔符

因爲<><STDIN>讀取文件、讀取標準輸入的時候老是自帶換行符,不少時候這個自帶的換行符都會帶來格式問題。因此,有必要在每次讀取數據時將行尾的換行符去掉,使用chomp便可。

例如:

foreach $line (<STDIN>) {
    chomp $line;
    print "$line read\n";
}

如下是執行結果:

[root@xuexi ~]# echo -e "malongshuai gaoxiaofang" | perl 26.plx 
malongshuai gaoxiaofang read

若是上面的例子中不加上chomp,那麼執行結果將像下面同樣:

[root@xuexi perlapp]# echo -e "malongshuai gaoxiaofang" | perl 26.plx 
malongshuai gaoxiaofang
 read

顯然,輸出格式和print語句中期待的輸出格式不同。

前面說過,能夠省略$line,讓其使用默認的參數變量$_,因此能夠這樣讀取來自perl命令行參數文件的數據:

foreach (<>) {
    chomp;
    print "$_ read\n";
}

8.命令行的操做模式

其實就是一行式。perl命令行加上"-e"選項,就能在perl命令行中直接寫perl表達式,例如:

echo "malongshuai" | perl -e '$name=<STDIN>;print $name;'

由於perl最爲人所知的就是它應用了各類符號的組合,讓人看着怪異無比,而這些符號放在命令行中極可能會被shell先解析,因此強烈建議"-e"後表達式使用單引號包圍,而不是雙引號。

更建議,若是能夠,不要使用perl命令行的方式,調試起來容易混亂。

perl如何使用正則進行匹配

使用=~符號表示要用右邊的正則表達式對左邊的數據進行匹配。正則表達式的書寫方式爲m//。關於m//,其中斜線能夠替換爲其它符號,規則以下:

  • 雙斜線能夠替換爲任意其它對應符號,例如對稱的括號類,m()m{},相同的標點類,m!!m%%等等
  • 只有當m模式採用雙斜線的時候,能夠省略m字母,即//等價於m//
  • 若是正則表達式中出現了和分隔符相同的字符,能夠轉義表達式中的符號,但更建議換分隔符,例如/http:\/\//轉換成m%http://%

因此要匹配內容,有如下兩種方式:

  • 方式一:使用data =~ m/reg/,能夠明確指定要對data對應的內容進行正則匹配
  • 方式二:直接/reg/,由於省略了參數,因此使用默認參數變量,它等價於$_ =~ m/reg/,也就是對$_保存的內容進行正則匹配

perl中匹配操做返回的是匹配成功與否,成功則返回真,匹配不成功則返回假。固然,perl提供了特殊變量容許訪問匹配到的內容,甚至匹配內容以前的數據、匹配內容以後的數據都提供了相關變量以便訪問。見下面的示例。

例如:

1.匹配給定字符串內容

$name = "hello gaoxiaofang";
if ($name =~ m/gao/){
    print "matched\n";
}

或者,直接將字符串拿來匹配:

"hello gaoxiaofang" =~ m/gao/;

2.匹配來自管道的每一行內容,匹配成功的行則輸出

foreach (<STDIN>){
    chomp;
    if (/gao/){
        print "$_ was matched 'gao'\n";
    }
}

上面使用了默認的參數變量$_,它表示foreach迭代的每一行數據;上面還簡寫的正則匹配方式/gao/,它等價於$_ =~ m/gao/

如下是執行結果:

[root@xuexi perlapp]# echo -e "malongshuai gaoxiaofang" | perl 26.plx  
malongshuai gaoxiaofang was matched 'gao'

3.匹配文件中每行數據

foreach (<>){
    chomp;
    if(/gao/){
        print "$_ was matched 'gao'\n";
    }
}

4.若是想要輸出匹配到的內容,可使用特殊變量$&來引用匹配到的內容,還可使用$`引用匹配前面部分的內容,$'引用匹配後面部分的內容

例如:

aAbBcC =~ /bB/

因爲匹配的內容是bB,匹配內容以前的部分是aA,匹配以後的部分是cC,因而能夠看做下面對應關係:

(aA)(bB)(cC)
 |    |   |
 $`   $&  $'

如下是使用這三個特殊變量的示例:

$name="aAbBcC";
if($name =~ /bB/){
    print "pre match: $` \n";
    print "match: $& \n";
    print "post match: $' \n";
}

須要注意的是,正則中通常都提供全局匹配的功能,perl中使用修飾符/g開啓。當開啓了全局匹配功能,這3個變量保存的值須要使用循環語句去遍歷,不然將只保存第一次匹配的內容。例如:

$name="aAbBcCbB";
if($name =~ /bB/g){        # 匹配完第一個bB就結束
    print "pre match: $` \n";
    print "match: $& \n";
    print "post match: $' \n";
}

while($name =~ /bB/g){   # 將迭代兩次
    print "pre match: $` \n";
    print "match: $& \n";
    print "post match: $' \n";
}

perl支持的正則

從這裏開始,正式介紹perl支持的正則。

出於方便,我所有都直接在perl程序內部定義待匹配的內容,若是想要匹配管道傳遞的輸入,或者匹配文件數據,請看上文獲取操做方法。

爲了完整性,每一節中我都是先把一大堆的內容列出來作個簡單介紹,而後再用示例解釋每一個(或某幾個)。但perl正則的內容太多,並且不少功能先後關聯,因此若是列出來的內容沒有在同一小節內介紹,那麼就是在後面須要的地方介紹。固然,也有些沒什麼用或者用的不多的功能(好比unicode相關的),通篇都不會介紹。

模式匹配修飾符

指定模式匹配的修飾符,能夠改變正則表達式的匹配行爲。例如,下面的i就是一種修飾符,它讓前面的正則REG匹配時忽略大小寫。

m/REG/i

perl總共支持如下幾種修飾符:msixpodualngc

  • i:匹配時忽略大小寫
  • g:全局匹配,默認狀況下,正則表達式"abc"匹配"abcdabc"字符串的時候,將之匹配左邊的abc,使用g將匹配兩個"abc"
  • c:在開啓g的狀況下,若是匹配失敗,將不重置搜索位置
  • m:多行匹配模式
  • s:讓.能夠匹配換行符"\n",也就是說該修飾符讓.真的能夠匹配任意字符
  • x:容許正則表達式使用空白符號,省得讓整個表達式難讀難懂,但這樣會讓本來的空白符號失去意義,這是可使用\s來表示空白
  • o:只編譯一次正則表達式
  • n:非捕獲模式
  • p:保存匹配的字符串到${^PREMATCH}${^MATCH}${^POSTMATCH}中,它們在結果上對應$`$&$',但性能上要更好
  • aul:分別表示用ASCII、Unicode和Locale的方式來解釋正則表達式,通常不用考慮這幾個修飾符
  • d:使用unicode或原生字符集,就像5.12和以前那樣,也不用考慮這個修飾符

這些修飾符能夠連用,連用時順序可隨意。例以下面兩行是等價的行爲:全局忽略大小寫的匹配行爲。

m/REG/ig
m/REG/gi

上面的修飾符,本節介紹igcmsxpo這幾個修飾符,n修飾符在後面分組捕獲的地方解釋,auld修飾符和字符集相關,不打算解釋。

i修飾符:忽略大小寫

該修飾符使得正則匹配的時候,忽略大小寫。

$name="aAbBcC";
if($name =~ m/ab/i){
    print "pre match: $` \n";     # 輸出a
    print "match: $& \n";         # 輸出Ab
    print "post match: $' \n";    # 輸出BcC
}

g和c修飾符以及\G

g修飾符(global)使得正則匹配的時候,對字符串作全局匹配,也就是說,即便前面匹配成功了,還會繼續向後匹配,看是否還能匹配成功。

例如,字符串"abcabc",正則表達式"ab",在默認狀況下(不是全局匹配)該正則在匹配到第一個ab後就結束了,若是使用了g修飾符,匹配完第一個ab,還會繼續向後匹配,並且正好還能匹配到第二個ab,因此最終有兩個ab被匹配成功。

要驗證屢次匹配,須要使用循環遍歷的方式,而不能用if語句:

$name="aAbBcCaBc";
while($name =~ m/ab/gi){
    print "pre match: $` \n";
    print "match: $& \n";
    print "post match: $' \n";
}

執行它,將輸出以下內容:

pre match: a 
match: Ab 
post match: BcCabd 
pre match: aAbBcC 
match: ab 
post match: d

如下內容,若是僅僅只是爲了學perl正則,那麼能夠跳過,由於很難,若是是學perl語言,那麼能夠繼續看下去。

實際上,在開啓了g全局匹配後,perl每次在成功匹配的時候都會記下匹配的字符位移,以便在下次匹配該內容時候,能夠從指定位移處繼續向後匹配。每次匹配成功後的位移值(pos的位移從0開始算,0位移表明的是第一個字符左邊的位置),均可以經過pos()函數獲取。若是本次匹配致使位移指針重置,pos將返回undef。

$name="123ab456";
$name =~ m/\d\d/g;     # 第一次匹配,匹配成功後記下位移
print "matched string: $&, position: ",pos $name,"\n";
$name =~ m/\d\d/g;     # 第二次匹配,匹配成功後記下位移
print "matched string: $&, position: ",pos $name,"\n";

執行它,將輸出以下內容:

matched string: 12, position: 2
matched string: 45, position: 7

因爲匹配失敗的時候,正則匹配操做會返回假,因此能夠做爲if或while等的條件語句。例如,改成while循環屢次匹配:

$name="123ab456";
while($name =~ m/\d\d/g){
    print "matched string: $&, position: ",pos $name,"\n";
}

默認全局匹配狀況下,當本次匹配失敗,位移指針將重置到起始位置0處,也就是說,下次匹配將從頭開始匹配。例如:

$txt="1234a56";
$txt =~ /\d\d/g;      # 匹配成功:12,位移向後移兩位
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d\d/g;      # 匹配成功:34,位移向後移兩位
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d\d\d/g;      # 匹配失敗,位移指針回到0處,pos()返回undef
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d/g;          # 匹配成功:1,位移向後移1位
print "matched $&: ",pos $txt,"\n";

執行上述程序,將輸出:

matched 12: 2
matched 34: 4
matched 34: 
matched 1: 1

若是"g"修飾符下同時使用"c"修飾符,也就是"gc",它表示全局匹配失敗的時候不重置位移指針。也就是說,本次匹配失敗後,位移指針會向後移一位,下次匹配將從後移的這個位置處開始匹配。當位移移到告終尾,將沒法再移動,此時位移指針將一直指向最後一個位置。

$txt="1234a56";
$txt =~ /\d\d/g;
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d\d/g;
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d\d\d/gc;   # 匹配失敗,位移向後移1位,$&和pos()保留上一次匹配成功的內容
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d/g;        # 匹配成功:5,位移向後移1位
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d/g;        # 匹配成功:6,位移向後移1位
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d/gc;        # 匹配失敗:位移沒法再後移,將一直指向最後一個位置
print "matched $&: ",pos $txt,"\n";

執行上述程序,將輸出:

matched 12: 2
matched 34: 4
matched 34: 4
matched 5: 6
matched 6: 7
matched 6: 7

繼續上面的問題,若是第三個匹配語句不是\d\d\d,而是"\d",它匹配字母a的時候也失敗,不用c修飾符的時候會重置位移嗎?顯然是不會。由於它會繼續向後匹配。因此該\G登場了。

默認全局匹配狀況下,匹配時是能夠跳過匹配失敗的字符繼續匹配的:當某個字符匹配失敗,它會後移一位繼續去匹配,直到匹配成功或匹配結束。

$txt="1234ab56";
$txt =~ /\d\d/g;
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d\d/g;
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d/g;       # 字母a匹配失敗,後移一位,字母b匹配失敗,後移一位,數值5匹配成功
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d/g;       # 數值6匹配成功
print "matched $&: ",pos $txt,"\n";

執行上述程序,將輸出:

matched 12: 2
matched 34: 4
matched 5: 7
matched 6: 8

能夠指定\G,使得本次匹配強制從位移處進行匹配,不容許跳過任何匹配失敗的字符。

  • 若是本次\G全局匹配成功,位移指針天然會後移
  • 若是本次\G全局匹配失敗,且沒有加上c修飾符,那麼位移指針將重置
  • 若是本次\G全局匹配失敗,且加上了c修飾符,那麼位移指針將卡在那不動

例如:

$txt="1234ab56";
$txt =~ /\d\d/g;
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d\d/g;
print "matched $&: ",pos $txt,"\n";
$txt =~ /\G\d/g;     # 強制從位移4開始匹配,沒法匹配字母a,但又不容許跳過
                     # 因此本次\G全局匹配失敗,因爲沒有修飾符c,指針重置
print "matched $&: ",pos $txt,"\n";
$txt =~ /\G\d/g;     # 指針回到0,強制從0處開始匹配,數值1能匹配成功
print "matched $&: ",pos $txt,"\n";

如下是輸出內容:

matched 12: 2
matched 34: 4
matched 34: 
matched 1: 1

若是將上面第三個匹配語句加上修飾符c,甚至後面的語句也都加上\G和c修飾符,那麼位移指針將卡在那個位置:

$txt="1234ab56";
$txt =~ /\d\d/g;
print "matched $&: ",pos $txt,"\n";
$txt =~ /\d\d/g;
print "matched $&: ",pos $txt,"\n";
$txt =~ /\G\d/gc;        # 匹配失敗,指針卡在原地
print "matched $&: ",pos $txt,"\n";
$txt =~ /\G\d/gc;        # 匹配失敗,指針繼續卡在原地
print "matched $&: ",pos $txt,"\n";
$txt =~ /\G\d/gc;        # 匹配失敗,指針繼續卡在原地
print "matched $&: ",pos $txt,"\n";

如下是輸出結果:

matched 12: 2
matched 34: 4
matched 34: 4
matched 34: 4
matched 34: 4

通常來講,全局匹配都會用循環去屢次迭代,和上面一次一次列出匹配表達式不同。因此,下面使用while循環的例子來對\G和c修飾符稍做解釋,其實理解了上面的內容,在循環中使用\G和c修飾符也同樣很容易理解。

$txt="1234ab56";
while($txt =~ m/\G\d\d/gc){
    print "matched: $&, ",pos $txt,"\n";
}

執行結果:

matched: 12, 2
matched: 34, 4

當第三輪循環匹配到a字母的時候,因爲使用了\G,致使匹配失敗,結束循環。

上面使用c與否是可有可無的,但若是這個while循環的後面後還有對$txt的匹配,那麼使用c修飾符與否就有關係了。例以下面兩段程序,返回結果不同:

$txt="1234ab56";
while($txt =~ m/\G\d\d/gc){   # 使用c修飾符
    print "matched: $&, ",pos $txt,"\n";
}

$txt =~ m/\G\d\d/gc;
print "matched: $&, ",pos $txt,"\n";

$txt="1234ab56";
while($txt =~ m/\G\d\d/g){   # 不使用c修飾符
    print "matched: $&, ",pos $txt,"\n";
}

$txt =~ m/\G\d\d/gc;
print "matched: $&, ",pos $txt,"\n";

m修飾符:多行匹配模式

正則表達式通常都只用來匹配單行數據,但有時候卻須要一次性匹配多行。好比匹配跨行單詞、匹配跨行詞組,匹配跨行的對稱分隔符(如一對括號)。

使用m修飾符能夠開啓多行匹配模式。

例如:

$txt="ab\ncd";
$txt =~ /a.*\nc/m;
print "===match start===\n$&\n===match end===\n";

執行,將輸出:

===match start===
ab
c
===match end===

關於多行匹配,須要注意的是元字符.默認狀況下沒法匹配換行符。可使用[\d\D]代替點,也能夠開啓s修飾符使得.能匹配換行符。

例如,下面兩個匹配輸出的結果和上面是一致的。

$txt="ab\ncd";
$txt =~ /a.*c/ms;
print "===match start===\n$&\n===match end===\n";

$txt="ab\ncd";
$txt =~ /a[\d\D]*c/m;
print "===match start===\n$&\n===match end===\n";

s修飾符

默認狀況下,.元字符是不能匹配換行符\n的,開啓了s修飾符功能後,可讓.匹配換行符。正如剛纔的那個例子:

$txt="ab\ncd";
$txt =~ /a.*c/m;        # 匹配失敗
print "===match start===\n$&\n===match end===\n";

$txt="ab\ncd";
$txt =~ /a.*c/ms;       # 匹配成功
print "===match start===\n$&\n===match end===\n";

x修飾符

正則表達式最爲人所抱怨的就是它的可讀性極差,不管你的正則能力有多強,看着一大堆亂七八糟的符號組合在一塊兒,都得一個符號一個符號地從左向右讀。

萬幸,perl正則支持表達式的分隔,甚至支持註釋,只需加上x修飾符便可。這時候正則表達式中出現的全部空白符號都不會看成正則的匹配對象,而是直接被忽略。若是想要匹配空白符號,可使用\s表示,或者將空格使用\Q...\E包圍。

例如,如下4個匹配操做是徹底等價的。

$ans="cat sheep tiger";
$ans =~ /(\w) *(\w) *(\w)/;       # 正常狀況下的匹配表達式
$ans =~ /(\w)\s*   (\w)\s*   (\w)/x;
$ans = ~ /
        (\w)\s*      # 能夠加上本行註釋:匹配第一個單詞
        (\w)\s*      # 能夠加上本行註釋:匹配第二個單詞
        (\w)         # 能夠加上本行註釋:匹配第三個單詞
        /x;
$ans =~ /
         (\w)\Q \E   # \Q \E強制將中間的空格看成字面符號被匹配
         (\w)\Q \E
         (\w)
        /x;

對於稍微複雜一些的正則表達式,經常都會使用x修飾符來加強其可讀性,最重要的是加上註釋。這一點真的很是人性化。

p修飾符

前面說過,經過3個特殊變量$`$&$'能夠保存匹配內容以前的內容,匹配內容以及匹配內容以後的內容。可是,只要使用了這3個變量中的任何一個,後面的全部分組效率都會下降。perl提供了一個p修飾符,能實現徹底相同的功能:

${^PREMATCH}    <=>   $`
${^MATCH}       <=>   $&
${^POSTMATCH}   <=>   $'

一個例子便可描述:

$ans="cat sheep tiger";
$ans =~ /sheep/p;
print "${^PREMATCH}\n";     # 輸出"cat "
print "${^MATCH}\n";        # 輸出"sheep"
print "${^POSTMATCH}\n";    # 輸出" tiger"

o修飾符

在較老的perl版本中,若是使用同一個正則表達式作屢次匹配,正則引擎將只屢次編譯正則表達式。不少時候正則表達式並不會改變,好比循環匹配文件中的行,這樣的屢次編譯致使性能降低很明顯,因而可使用o修飾符讓正則引擎對同一個正則表達式不重複編譯。

在perl5.6中,默認狀況下對同一正則表達式只編譯一次,但一樣能夠指定o修飾符,使得即便正則表達式變化了也不要從新編譯。

通常狀況下,能夠無視這個修飾符。

範圍模式匹配修飾符(?imsx-imsx:pattern)

前文介紹的修飾符adluoimsxpngc都是放在m//{FLAG}的flag處的,放在這個位置會對整個正則表達式產生影響,因此它的做用範圍有點廣。

例如m/pattern1 pattern2/i的i修飾符會影響pattern1和pattern2。

perl容許咱們定義只在必定範圍內生效的修飾符,方式是(?imsx:pattern)(?-imsx:pattern)(?imsx-imsx:pattern),其中加上-表示去除這個修飾符的影響。這裏只列出了imsx,由於這幾個最經常使用,其餘的修飾符也同樣有效。

例如,對於待匹配字符串"Hello world gaoxiaofang",使用如下幾種模式去匹配的話:

/(?i:hello) world/
表示匹配hello時,可忽略大小寫,但匹配world時仍然區分大小寫。因此匹配成功

/(?ims:hello.)world/
表示能夠跨行匹配helloworld,也能夠匹配單行的hellosworld,且hello部分忽略大小寫。因此匹配成功

/(?i:hello (?-i:world) gaoxiaoFANG)/
表示在第二個括號以前,可用忽略大小寫進行匹配,但由於第二個括號裏指明瞭去除i的影響,因此對world的匹配會區分大小寫,可是對gaoxiaofang部分的匹配又不區分大小寫。因此匹配成功

/(?i:hello (?-i:world) gaoxiao)FANG/
和前面的相似,可是將"FANG"放到了括號外,意味着這部分要區分大小寫。因此匹配失敗

perl支持的反斜線序列

1.錨定類的反斜線序列

所謂錨定,是指它匹配的是位置,而非字符,好比錨定行首的意思是匹配第一個字母前的空字符。也就是不少人說的"零寬斷言(zero-width assertions)"。

  • \b:匹配單詞邊界處的空字符
  • \B:匹配非單詞邊界處的空字符
  • \<:匹配單詞開頭處的空字符
  • \>:匹配單詞結尾處的空字
  • \A:匹配絕對行首,換句話說,就是輸入內容的開頭
  • \z:匹配絕對行尾,換句話說,就是輸入內容的絕對尾部
  • \Z:匹配絕對行尾或絕對行尾換行符前的位置,換句話說,就是輸入內容的尾部
  • \G:強制從位移指針處進行匹配,詳細內容見g和c修飾符以及\G

主要解釋下\A \z \Z,其它的屬於基礎正則的內容,很少作解釋了。

\A \z \Z^ $的區別主要體如今多行模式下。在多行模式下:

$txt = "abcd\nABCD\n";
$txt1 = "abcd\nABCD";

$txt =~ /^ABC*/;   # 沒法匹配
$txt =~ /^ABC*/m;  # 匹配

$txt =~ /\Aabc/;   # 匹配
$txt =~ /\Aabc/m;  # 匹配
$txt =~ /\AABC/m;  # 沒法匹配

$txt =~ /cd\n$/m;  # 不匹配
$txt =~ /cd$\n/m;  # 不匹配
$txt =~ /cd$/m;    # 匹配

$txt =~ /CD\Z\n/m  # 匹配
$txt =~ /CD\Z\n\Z/m; # 匹配
$txt =~ /CD\n\z/m; # 匹配

$txt1 =~ /CD\Z/m;  # 匹配
$txt1 =~ /CD\z/m;  # 匹配

從上面的$匹配示例可知,$表明的行尾,其實它在有換行符的時候匹配"\n",而不是"\n"的前、後,在沒有換行符的時候,匹配行尾。

2.字符匹配反斜線序列

固然,除了如下這幾種,還有\v \V \h \H \R \p \c \X,這些基本不會用上,因此都不會在本文解釋。

  • \w:匹配單詞構成部分,等價於[_[:alnum:]]
  • \W:匹配非單詞構成部分,等價於[^_[:alnum:]]
  • \s:匹配空白字符,等價於[[:space:]]
  • \S:匹配非空白字符,等價於[^[:space:]]
  • \d:匹配數字,等價於[0-9]
  • \D:匹配非數字,等價於[^0-9]
  • \N:不匹配換行符,等價於[^\n]。但\N{NAME}有特殊意義,表示匹配已命名(名爲NAME)的unicode字符序列,本文不介紹該特殊用法

因爲元字符.默認沒法匹配換行符,因此須要匹配換行符的時候,可使用特殊組合[\d\D]或者(\n|\N)來替換.,換句話說,若是想匹配任意長度的任意字符,能夠換成[\d\D]*或者(\n|\N)*,固然,前提是必須支持這3個反斜線序列。

之因此不用[\n\N]替代元字符.,是由於\N有特殊意義,不能隨意接符號和字母。

3.分組引用的反斜線序列

  • \1:反向引用,其中1能夠替換爲任意一個正整數,即便超出9,例如\111表示匹配第111個分組
  • \g1\g{1}:也是反向引用,只不過這種寫法能夠避免歧義,例如\g{1}11表示匹配第一個分組內容後兩個數字1
  • \g{-1}:還可使用負數,表示距離\g左邊的分組號,也就是相對距離。例如(abc)([a-z])\g{-1}中的\g引用的是[a-z],若是-1換成-2,則引用的abc
  • \g{name}:引用已命名的分組(命名捕獲),其中name爲分組的名稱
  • \k<name>:同上,引用已命名的分組(命名捕獲),其中name爲分組的名稱
  • \K:不要將\K左邊的內容放進$&。換句話說,\K左邊的內容即便匹配成功了,也會重置匹配的位置

\1表示引用第一個分組,\11表示引用第11個分組,在基礎正則中,是不支持引用超出9個分組的,但顯然perl會將\11的第二個1解析爲引用,以便能引用更多分組。

同理\g1\g11,只是使用\g引用的方式能夠加上大括號使引用變得更安全,更易讀,且\g可使用負數來表示從右向左相對引用。這樣在\g{-2}的左邊添加新的分組括號時,無須修改引用表達式。

此處暫時還沒介紹到命名分組,因此\g{name}\k<name>留在後面再介紹。

\K表示強制中斷前面已完成的匹配。例如"abc22ABC" =~ /abc\K2.*/;,雖然abc三個字母也被匹配,若是沒有\K,這3個字母將放進$&中,可是\K使得匹配完abc後當即切斷前面的匹配,也就是從c字母后面開始從新匹配,因此這裏匹配的結果是22ABC。

再例如,"abc123abcfoo"=~ /(abc)123\K\g1foo/;,它匹配到123後被切斷,可是分組引用還能夠繼續引用,因此匹配的結果是"abcfoo"。

貪婪匹配、非貪婪匹配、佔有優先匹配

在基礎正則中,那些能匹配屢次的量詞都會匹配最長內容。這種儘可能多匹配的行爲稱爲"貪婪匹配"(greedy match)。

例如字符串"aa1122ccbb",用正則表達式a.*c去匹配這個字符串,其中的.*將直接從第二個字母a開始匹配到最結尾的b,由於從第二個字母a開始到最後一個字母b都符合.*的匹配模式。再而後,去匹配字母c,但由於已經把全部字母匹配完了,只能回退一個字母一個字母地釋放,每釋放一個就匹配一次字母c,發現回退釋放到倒數第三個字母就能知足匹配要求,因而這裏的.*最終匹配的內容是"a1122cc"。

上面涉及到回溯的概念,也就是將那些已經被量詞匹配的內容回退釋放。

上面描述的是貪婪匹配行爲,還有非貪婪匹配、佔有優先匹配,如下簡單描述下他們的意義:

  • 非貪婪匹配:(lazy match,reluctant)儘量少地匹配,也叫作懶惰匹配
  • 佔有優先匹配:(possessive)佔有優先和固化分組是相同的,只要佔有了就再也不交換,不容許進行回溯。相關內容見後文"固化分組"

有必要搞清楚這幾種匹配模式在匹配機制上的區別:

  • 貪婪匹配:對於那些量詞,將一次性從左到右匹配到最大長度,而後再往回回溯釋放
  • 非貪婪匹配:對於那些量詞,將從左向右逐字符匹配最短長度,而後直接結束此次的量詞匹配行爲
  • 佔有優先匹配:按照貪婪模式匹配,匹配後內容就鎖住,不進行回溯(後文固化分組有具體示例)

除了上面描述的*量詞會進行貪婪匹配,其餘全部能進行屢次匹配的量詞能夠選擇貪婪匹配模式、非貪婪匹配模式和佔有優先匹配模式,只需選擇對應的量詞元字符便可。以下:

(量詞後加上?)             (量詞後加上+)
  貪婪匹配量詞           非貪婪匹配量詞           佔有優先匹配量詞
-----------------------------------------------------------------
      *                    *?                         *+
      ?                    ??                         ?+
      +                    +?                         ++
      {M,}                 {M,}?                      {M,}+
      {M,N}                {M,N}?                     {M,N}+
      {N}                  {N}?                       {N}+

幾點須要說明:

  • 非貪婪匹配時的{M,}?{M,N}?,它們是等價的,由於最多隻匹配M次
  • 在perl中不支持{,N}的模式,因此也沒有對應的非貪婪和佔有優先匹配模式
  • 關於{N}這個量詞,因爲是精確匹配N次,因此貪婪與否對最終結果可有可無,可是卻影響匹配時的行爲:貪婪匹配最長,須要回溯,非貪婪匹配最短,不回溯,佔有優先匹配最長不回溯。

看如下示例便可理解貪婪和非貪婪匹配的行爲:

$str="abc123abc1234";

# greedy match
if( $str =~ /(a\w*3)/){
    print "$&\n";       # abc123abc123
}

# lazy match
if( $str =~ /(a\w*?3)/){
    print "$&\n";      # abc123
}

如下是佔有優先匹配模式的示例:

$str="abc123abc1234";

if( $str =~ /a\w*+/){     # 成功
        print "possessive1: $&\n";
}
if( $str =~ /a\w*+3/){    # 失敗
        print "possesive2: $&\n";
}

因此,在使用佔有優先匹配模式時,它後面不該該跟其餘表達式,例如a*+x永遠匹配不了東西。絕大多數時候都是不會回溯的。可是少數狀況下,它並不是強制鎖住回溯,這個和正則引擎匹配原理有關,本文很少作解釋。

另外,固化分組和佔有優先並不徹底等價,它們只是匹配行爲相同:匹配後不回溯。具體可對比後文對應內容。

perl的分組捕獲和分組引用

分組的基本應用

在基礎正則中,使用括號能夠對匹配的內容進行分組,這種行爲稱爲分組捕獲。捕獲後能夠經過\1這種反向引用方式去引用(訪問)保存在分組中的匹配結果。

例如:

"abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\1\2/;

在perl中,還可使用\gN的方式來反向引用分組,這個在上一節"反斜線序列"中已經解釋過了。例如,如下是和上面等價的幾種寫法:

"abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\g1\g2/;
"abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\g{1}\g{2}/;
"abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\g{-2}\g{-1}/;

perl還會把分組的內容放進perl自帶的特殊變量$1,$2,...,$N中,它們和\1,\2,...\N匹配成功時的結果上沒有區別,可是\N這種類型的反向引用只在正則匹配中有效,正則匹配結束後就消亡了,而$N由於是perl的變量,即便正則已經退出匹配,也依然能夠引用。因此,咱們可使用$N的方式來輸出分組匹配的結果:

"abc11ddabc11" =~ /([a-z]*)([0-9]*)dd\1\2/;
print "first group \\1: $1\n";
print "second group \\2: $2\n";

有兩點須要注意:

  • 這些分組可能捕獲到的是空值(好比那些容許匹配0次的量詞),可是整個匹配是成功的。這時候引用分組時,獲得的結果也將是空值
  • 當分組匹配失敗的時候,\1會在識別括號的時候重置,而$1仍保存上一次分組成功的值

第一點,示例可知:

"abcde" =~ /([0-9]*)de/;
print "null group: $1\n";

第二點,從機制上去分析。\1是每一個正則匹配都相互獨立的,而$1則保存分組捕獲成功的值,即便此次值是上次捕獲的。

這裏稍微解釋下正則匹配關於分組捕獲的匹配過程:

例如,匹配表達式"12abc22abc" =~ /\d(abc)\d\d\1/;,當正則引擎去匹配數據時:
1.首先匹配第一個數字1,發現符合\d,因而繼續用(abc)去匹配字符串,由於發現了是分組括號,因而會將第二個字符2放進分組,發現不匹配字母a,因而匹配失敗,丟棄這個分組中的內容。
2.正則引擎繼續向後匹配數值2,發現符合\d,因而用(abc)去匹配字符串,接着會將第三個字符a放進分組,發現能匹配,繼續匹配字符串中的b、c發現都能匹配,因而分組捕獲完成,將其賦值給$1,以後就能用\1$1去引用這個分組的內容。
3.後面繼續去匹配\d\d\1,直到匹配結束。

固然,具體匹配的過程不會真的這麼簡單,它會有一些優化匹配方式,以上只是用邏輯去描述匹配的過程。

perl中更強大的分組捕獲

在perl中,支持的分組捕獲更強大、更完整,它除了支持普通分組(也就是直接用括號的分組),還支持:

  • 命名捕獲(?<NAME>...):捕獲後放進一個已分配好名稱(即NAME)的分組中,之後可使用這個名稱來引用這個分組,如\g{NAME}引用
  • 匿名捕獲(?:...):僅分組,不捕獲,因此後面沒法再引用這個捕獲
  • 固化分組(?>...):一匹配成功就永不交回內容(用回溯的想法理解很容易)
匿名捕獲

匿名捕獲是指僅分組,不捕獲。由於不捕獲,因此沒法使用反向引用,也不會將分組結果賦值給$1這種特殊變量。

雖然有了分組捕獲功能,就能夠實現任何需求,但有時候可讓這種行爲變得更人性化,減小維護力度。

例如字符串"xiaofang or longshuai",使用模式/(\w+) or (\w+)/去捕獲,用$1$2分別引用or左右兩個單詞:

$str = "xiaofang or longshuai";
if ($str =~ /(\w+) or (\w+)/){
    print "name1: $1, name2: $2\n";
}

但若是需求是中間的關係or也能夠換成and,爲了同時知足and和or兩種需求,使用模式/(\w+) (and|or) (\w+)/去匹配,可是這時引用的序號就得由$2變爲$3

$str = "xiaofang or longshuai";
if ($str =~ /(\w+) (or|and) (\w+)/){
    print "name1: $1, name2: $3\n";
}

若是使用匿名捕獲,對and和or這樣可有可無,卻有可能改變匹配行爲的內容,能夠將其放進一個無關的分組中。這樣不會對原有的其他正則表達式產生任何影響:

$str = "xiaofang or longshuai";
if ($str =~ /(\w+) (?:or|and) (\w+)/){
    print "name1: $1, name2: $2\n";
}

注意上面仍然使用$2引用第三個括號。

一樣,若是要在正則內部使用反向引用,也同樣使用\2來引用第三個括號。

另外,在前文還介紹過一個n修飾符,它也表示非捕獲僅分組行爲。但它只對普通分組有效,對命名分組無效。且由於它是修飾符,它會使全部的普通分組都變成非捕獲模式。

$str = "xiaofang or longshuai";
if ($str =~ /(\w+) (or|and) (\w+)/n){
    print "name1: $1, name2: $2\n";
}

因爲上面開啓了n修飾符,使得3個普通分組括號都變成非捕獲僅分組行爲,因此\1$1都沒法使用。除非正則中使用了命名分組。

命名捕獲

命名捕獲是指將捕獲到的內容放進分組,這個分組是有名稱的分組,因此後面可使用分組名去引用已捕獲進這個分組的內容。除此以外,和普通分組並沒有區別。

當要進行命名捕獲時,使用(?<NAME>)的方式替代之前的分組括號()便可。例如,要匹配abc並將其分組,之前普通分組的方式是(abc),若是將其放進命名爲name1的分組中:(?<name1>abc)

當使用命名捕獲的時候,要在正則內部引用這個命名捕獲,除了可使用序號類的絕對引用(如\1\g1\g{1}),還可使用如下任意一種按名稱引用方式:

  • \g{NAME}
  • \k{NAME}
  • \k<NAME>
  • \k'NAME'

若是要在正則外部引用這個命名捕獲,除了可使用序號類的絕對應用(如$1),還可使用$+{NAME}的方式。

實際上,後一種引用方式的本質是perl將命名捕獲的內容放進了一個名爲%+的特殊hash類型中,因此可使用$+{NAME}的方式引用,若是你不知道這一點,那就無視與此相關的內容便可,不過都很簡單,一看就懂。

例如:

$str = "ma xiaofang or ma longshuai";
if ($str =~ /
            (?<firstname>\w+)\s  # firstname -> ma
            (?<name1>\w+)\s      # name1 -> xiaofang
            (?:or|and)\s      # group only, no capture
            \g1\s                # \g1 -> ma
            (?<name2>\w+)        # name2 -> longshuai
            /x){
    print "$1\n";
    print "$2\n";
    print "$3\n";
    # 或者指定名稱來引用
    print "$+{firstname}\n$+{name1}\n$+{name2}\n";
}

其中上述代碼中的\g1還能夠替換爲\1\g{firstname}\k{firstname}\k<firstname>

經過使用命名捕獲,能夠無視序號,直接使用名稱便可準確引用。

固化分組

首先固化分組不是一種分組,因此沒法去引用它。它和"佔有優先"匹配模式(貪婪匹配、惰性匹配、佔有優先匹配三種匹配模式,見後文)是等價的除了這兩種稱呼,在不一樣的書、不一樣的語言裏還有一種稱呼:原子匹配。

它的表示形式相似於分組(?>),因此有些地方將其稱呼爲"固化分組"。再次說明,固化分組不是分組,沒法進行引用。若是非要將其看做是分組,能夠將其理解爲被限定的匿名分組:不捕獲,只分組。

  1. 按照"佔有優先"的字面意義來理解比較容易:只要匹配成功了,就毫不回溯。
  2. 若是按照固化分組的概念來理解,就是將匹配成功的內容放進分組後,將其固定,不容許進行回溯。可是須要注意,這裏的不回溯是放進分組中的內容不會回溯給分組外面,而分組內部的內容是能夠回溯的

若是不知道什麼是回溯,看完下面的例子就明白。

例如"hello world"能夠被hel.* world成功匹配,但不能被hel(?>.*) world匹配。由於正常狀況下,.*匹配到全部內容,而後往回釋放已匹配的內容直到釋放完空格爲止,這種往回釋放字符的行爲在正則術語中稱爲"回溯"。而固化分組後,.*已匹配後面全部內容,這些內容一經匹配毫不交回,即沒法回溯。

可是,若是正則表達式是hel(?>.* world),即將原來分組外面的內容放進了分組內部,這時在分組內部是會回溯的,也就是說能匹配"hello world"。

$str="ma longshuai gao xiaofang";
if($str =~ /ma (?>long.*)/){     # 成功
    print "matched\n";
}

if($str =~ /ma (?>long.*)gao/){   # 失敗
    print "matched\n";
}

if($str =~ /ma (?>long.*gao)/){   # 成功
    print "matched\n";
}

if($str =~ /ma (?>long.*g)ao/){   # 成功
    print "matched\n";
}

固化分組看上去挺簡單的,此處也僅介紹了它最簡單的形式。但實際上固化分組很複雜,它涉及了很是複雜的正則引擎匹配原理和回溯機制。若是有興趣,能夠閱讀《精通正則表達式》一書的第四章。

環視錨定(斷言)

"環視"錨定,即lookaround anchor,也稱爲"零寬斷言",它表示匹配的是位置,不是字符。

  • (?=...):表示從左向右的順序環視。例如(?=\d)表示當前字符的右邊是一個數字時就知足條件
  • (?!...):表示順序環視的取反。如(?!\d)表示當前字符的右邊不是一個數字時就知足條件
  • (?<=...):表示從右向左的逆序環視。例如(?<=\d)表示當前字符的左邊是一個數字時就知足條件
  • (?<!)...:表示逆序環視的取反。如(?<!\d)表示當前字符的左邊不是一個數字時就知足條件

關於"環視"錨定,最須要注意的一點是匹配的結果不佔用任何字符,它僅僅只是錨定位置。

例如"your name is longshuai MA"和"your name is longfei MA"。使用(?=longshuai)將能錨定第一個句子中單詞"longshuai"前面的空字符,但它的匹配結果是"longshuai"前的空白字符,因此(?=longshuai)long才能表明"long"這幾個字符串,因此僅對於此處的兩個句子,long(?=shuai)(?=longshuai)long是等價的。

通常爲了方便理解,在順序環視的時候會將匹配內容放在錨定括號的左邊(如long(?=longshuai)),在逆序環視的時候會將匹配的內容放在錨定括號的右邊(如(?<=long)shuai)。

另外,不管是哪一種錨定,都是從左向右匹配再作回溯的(假設容許回溯),即便是逆序環視。

例如:

$str="abc123abcc12c34";

# 順序環視
$str =~ /a.*c(?=\d)/;     # abc123abcc12c
print "$&\n";

# 順序否認環視
$str =~ /a.*c(?!\d)/;     # abc123abc
print "$&\n";

# 逆序環視,這裏能逆序匹配成功,靠的是錨定括號後面的c
$str =~ /a.*(?<=\d)c/;    # abc123abcc12c
print "$&\n";

# 逆序否認環視
$str =~ /a.*(?<!\d)c/;    # abc123abcc
print "$&\n";

逆序環視的表達式必須只能表示固定長度的字符串。例如(?<=word)(?<=word|word)能夠,但(?<=word?)不能夠,由於?匹配0或1長度,長度不定,它沒法對左邊是word仍是wordx作正確判斷。

$str="hello worlds Gaoxiaofang";
$str =~ /he.*(?<=worlds?) Gao/;         # 報錯
$str =~ /he.*(?<=worlds|world) Gao/;    # 報錯

在PCRE中,這種變長的逆序環視錨定可重寫爲(?<=word|words),但perl中不容許,由於perl嚴格要求長度必須固定。

\Q...\E

perl中的\Q...\E用來強制包圍一段字符,使得裏面的正則符號都當作普通字符,不會有特殊意義,它是一種很是強的引用。但注意,它沒法強制變量的替換。

例如:

$sub="world";
$str="hello worlds gaoxiaofang";
$str =~ /\Q$sub\E/;  # $sub會替換,因此匹配成功world
$str =~ /\Q$sub.\E/; # 元字符"."被當作普通的字符,因此沒法匹配

qr//建立正則對象

由於能夠在正則模式中使用變量替換,因此咱們能夠將正則中的一部分表達式事先保存在變量中。例如:

$str="hello worlds gaoxiaofang";
$pattern="w.*d";
$str =~ /$pattern/;
print "$&\n";

可是,這樣缺陷很大,在保存正則表達式的變量中存放的特殊字符要防止有特殊意義。例如,當使用m//的方式作匹配分隔符時,不能在變量中保存/,除非轉義。

perl提供了qr/pattern/的功能,它把pattern部分構建成一個正則表達式對象,而後就能夠在正則表達式中直接引用這個對象,更方便的是能夠將這個對象保存到變量中,經過引用變量的方式來引用這個已保存好的正則對象。

$str="hello worlds gaoxiaofang";

# 直接做爲正則表達式
$str =~ qr/w.*d/;
print "$&\n";

# 保存爲變量,再做爲正則表達式
$pattern=qr/w.*d/;
$str =~ $pattern;    # (1)
$str =~ /$pattern/;  # (2)
print "$&\n";

# 保存爲變量,做爲正則表達式的一部分
$pattern=qr/w.*d/;
$str =~ /hel.* $pattern/;
print "$&\n";

還容許爲這個正則對象設置修飾符,好比忽略大小寫的匹配修飾符爲i,這樣在真正匹配的時候,就只有這一部分正則對象會忽略大小寫,其他部分仍然區分大小寫。

$str="HELLO wORLDs gaoxiaofang";

$pattern=qr/w.*d/i;         # 忽略大小寫

$str =~ /HEL.* $pattern/;   # 匹配成功,$pattern部分忽略大小寫
$str =~ /hel.* $pattern/;   # 匹配失敗
$str =~ /hel.* $pattern/i;  # 匹配成功,全部都忽略大小寫
相關文章
相關標籤/搜索