第七章正規表達式
Perl 有許多差別于其它語言的特性。在所有這些特性中,最重要的一條是對正規表達式的支援。它允許你友善,快捷的處
理字元串相關的問題。
但是獲得這種能力是需要付出代價的。正規表達式(regular expressions)是一種特殊語言寫成的程式,内嵌于Perl 之中(是的,
你要學習一門新的程式語言◆。慶幸的是,它非常簡單。)。從本章開始,我們将進入正規表達式的世界,現在你可以暫時
忘掉Perl。下一章中,我們将介紹如何在Perl 中使用正規表達式。
◆ 一些人可能争論說正規表達式不是完備的程式語言。我們不準備在這裡争論這個問題。
正規表達式不僅僅是Perl 的一部分;在sed, awk, procmail, grep, 以及許多程式員文本編輯器(programmer’s text editor)如vi,
emacs,還有一些更深奧的程式中都有它的蹤影。注意觀察,你可以發現許多工具都支援正規表達式,如Web 上的搜尋引
擎(通常由Perl 書寫),email 用戶端,等等。不幸的消息是,不同的正規表達式的實作中,文法會有些微的不同,是以需
要學習其不同的地方,例如需要還是不需要反斜線(\)。
7.1 什麼是正規表達式?
正規表達式,在Perl 中通常被稱為模式(pattern):某個模闆是否比對某個字元串◆。由于存在無限的字元串,某個給定的模
式将這些字元串分成兩類:一類是能比對的,一類是不能比對的。這裡沒有,或者,大概,幾乎那樣的比對:要麼比對,
要麼不比對。
◆某些學究可能認為這個定義不夠嚴格。他們可能會說Perl 的模式根本不是真正意義上的正規表達式。如果你想知道更多的關于正則表達
式的資訊可以參看Jeffrey Friedl(O’Reily)的書籍《掌握正規表達式》(Mastering Regular Expessions)。
一個模式可以比對多個字元串:1個,2個,3個,上百個,或者無限個。也可能比對除了1個,多個,或者無限個字元
串之外的所有字元串◆。我們将正規表達式看作一種由簡單語言實作的程式,這種語言隻有一個任務:查找某個字元串,
傳回“比對上(it matches)”或者“不比對(it doesnot match)”◆。這就是它完成的所有工作。
◆将學習到,你可以讓某個模式永遠比對或者永不比對。在極少情況下,這也是有用的,但通常是錯誤。
◆程式也傳回一些資訊給Perl。其中的一種資訊是“正規表達式記憶體(regular expressions memories)”,這将在後面會學習到。
你可能在使用Unix 的grep 指令時,見過正規表達式,它會輸出比對上模式的行。例如,如果想檢視某個給定檔案中是否
某行提到過flint,同時同一行後面提到stone,那你可以如下的使用grep 指令:
$grep‘flint.*stone’chapter*.txt
chapter3.txt:a piece of flint, a stone which may be used to start a fire by striking
Perl 語言入門(第四版)
[email protected] 93 / 201 9/21/2006
chapter3.txt:found obsidian, flint, granite, and small stones of basaltic rock, which
chapter9.txt:a flintlock rifle in poor condition. The sandstone mantle held several
不要将正規表達式和shell 中的檔案名比對模式,globs 混淆了。通常glob 是指,在Unix shell 下輸入*.pm 将比對所有結尾
為.pm 的檔案名。上一個例子使用的glob 為chapter*.txt。(你可能已經注意到我們将模式括起來了,來防止shell 将其作為
glob 來處理)雖然glob 和正規表達式有許多相同的符号,但其作用并不相同◆。第十二章中将介紹globs。
◆globs 有時也被稱作模式。但嚴重的問題是,某些面向初級使用者的書籍(可能是菜鳥寫得)将globs 叫做“正規表達式”,這絕對是錯誤
的。
7.2 使用簡單的模式
要比對某個模式(正規表達式)和$_的關系,可以将模式放在正斜線(//)之間,如下:
$_ =“yabba dabba doo”;
if(/abba/){
print “It matched!\n”;
}
表達式/abba/将在$_尋找這四個字母。如果找到,則傳回true,在本例中,它出現了不止一次,但結果沒什麼不同。總之,
如果找到了,則比對上;如果沒找到,則沒比對上。
由于模式比對通常傳回true 或false,是以經常用在if 或while 的條件表達式部分。
所有在雙引号中的轉義字元在模式中均有效,是以你可以使用/coke\tsprite/來比對11 個字元的字元串coke, tab(制表符),
sprite。
7.2.1 元字元
如果模式隻能比對字面上的字元串,則其用處不會太大。這也是引入特殊字元的原因,它們被叫做元字元(metacharacters),
在正規表達式中具有特殊的含義。
例如,點(.)是通配符,它可以比對任何單個的字元,但不包括換行符(“\n”)。是以,模式/bet.y/将比對betty。同時也比對betsy,
bet=y, bet.y,或者說任意字元串後接bet,然後是任意的單個字元(不包括換行符),後接y。它不會比對bety,betsey,因為
t 和y 之間不是一個字元。點(.)隻比對一個字元。
如果想比對句号(英語中句号就是一個點:譯者注),可以使用點(.)。但由于點(.)可以比對任意的單個字元(除換行符外),
則其結果比你希望的要多。如果隻希望點(.)比對句号,可以使用反斜線。這條規則對Perl 正規表達式中所有元字元均有效:
元字元前使用反斜線将使它變成普通的字元。如,模式/3\.14159/中的點(.)即不是通配符。
反斜線是第二個元字元。如果需要真正的反斜線,需要重複使用兩個反斜線,這和Perl 中其它情況下是一樣的。
Perl 語言入門(第四版)
[email protected] 94 / 201 9/21/2006
7.2.2 簡單的量詞
通常,需要模式中某些串是可以重複的。星号(*)表示比對前一項0次或者多次。是以,/fred\t*barney/将比對上fred 和barney
之間有任意個制表位(tab)的字元串。它可以比對“fred\tbarney”,其間有一個tab;比對“fred\t\tbarney”,其間有兩個制表位;
“fred\t\t\tbarney”其間有三個制表位;“fredbarney”,其間什麼也沒有。這是由于星号(*)是指“0 個或者多個”,是以其間可以
是任意個制表符,但不能是其它的字元。可以這樣看待星号(*):“前面的東西,重複任意次數,包括0 次”(因為*号在數
學上是乘法運算符)。
如果希望包括不同的字元,怎麼辦呢?點(.)可以比對任何單字元◆,是以.*将比對任意字元任意多數。這就是說模式
/fred.*barney/将比對fred,和barney 之間有任意多個任意字元(不含換行符)的字元串。任意行如果前面有fred,後面有barney,
其間為任意字元(字元串)都将比對上。我們将.*叫做“任意字元串比對模式”,因為任意的字元串均能被比對上(不包括
換行符)。
◆除了換行符。以後我們将不再提醒你,因為你已經知道了。許多時候你不用擔心這個問題,因為通常你的字元串中并不含有換行符。但
不應該把這個細節忘掉,因為說不定某人就在你的字元串中加入了換行符,是以應當記住點(.)不會比對換行符。
星号的正是叫法是數量詞(quantifier),意指其可以指代多個前面的項。它不是唯一的數量詞,加(+)也是。加(+)的意思是可
以比對前面一項的一個或多個:/fred +barney/意思是fred 和barney 之間由空格分開,且隻能是空格。(空格不是元字元)。
它不會比對fredbarney,因為加(+)意指一個或多個,是以至少是一個。可以這樣看待加(+):“最後一項,(可選的)至少還
有一項。”
還有第三個數量詞,其限制性更強。它是問号(?),其含義是前面一個項出現一次,或者不出現。也就是說,前面這個項出
現1 次或者0 次,此外不會有其它情況。是以,/barm-?bamm/隻比對:bamm-bamm 或bammbamm。這很容易記住:“前面
的這個項,出現?或者不出現?”
這三個數量詞必須緊跟在某些項的後面,因為它是指前面項重複的次數。
7.2.3 模式中的分組
括号也是元字元。在數學中,括号(())用來表示分組。例如,模式/fred+/能比對上如fredddddddd,這樣的字元串,但這種字
符串在實際中沒有什麼用途。模式/(fred)+/能比對上像fredfredfred 這樣的字元串,這更可能是你所希望的。那麼模式/(fred)*/
呢?它将比對上像hello,world 這樣的字元串◆。
◆星号(*)意指比對上0 次或者多次fred。當為0 時,那什麼字元串都能被比對上。這個模式能比對上任何字元串,甚至是空串。
7.2.4 選擇符
豎線(|),在這種用法中通常被讀作“或(or)”,意思是比對左邊的或者右邊的。如果豎線左邊沒有比對上,則比對右邊。因
此,/fred|barney|betty/将比對出現過fred,或者barney,或者betty 的字元串。
Perl 語言入門(第四版)
[email protected] 95 / 201 9/21/2006
現在你可以書寫像/fred( |\t)+barney/這樣的模式,它将比對fred,barney 以及中間由空格,制表符(tab),或者二者混合所組
成的字元串。加(+)是指重複1 次或多次;每重複一次,( |\t)則有可能比對一個空格,或者一個制表符◆。但fred 和barney
之間這些字元中(空格,制表符)的其中之一必須出現一次。
◆如果使用字元類(character class)進行這種比對将更有效,這在本章後面會介紹。
如果希望fred 和barney 之間的字元是一樣的,可以将模式寫成/fred( +|\t+)barney/。在本例中,分隔符必須全是空格或者全
是制表符。
模式/fred (and|or) barney/能比對如下兩種字元串:fred and barney, fred or barney◆。也可以将模式寫成: /fred and barney|fred
or barney/,但這樣書寫的字元更多。并且其效率也更低,這依賴于正規表達式引擎中所使用的優化方法。
◆單詞and 和or 在正規表達式中不是操作符!它們在正規表達式中就是其本來的含義:單詞and,or。
7.3 字元類
字元類,是方括号[]中的一列字元,可以比對上括号内出現的任意單個字元。它比對一個字元,但這個字元可以是列中的
任意一個。
例如,字元類[abcwxyz]可以比對上括号内七個字母中的任意一個。為了友善,我們可以使用連字号(-)來表示某個範圍的字
母,是以上例也可以寫做[a-cw-z]。上面例子省略的字元不多,但像[a-zA-Z]将非常友善,利用它不需要輸入52 個字元◆。
你可以使用和雙引号相同的字元簡寫方法,例如類[\000-\177]可以比對上任意的七比特的ASCII 字元。◆。當然,字元類
隻是模式的一部分,單獨的字元類在Perl 中沒什麼實際的意義。例如,你可能見到如下的代碼:
◆注意這52 個字母不包括Å, É, Î, Ø, 和Ü。如果允許處理Unicode,則上述字元的範圍将自動的變化,來做正确的工作。
◆這裡,使用的是ASCII 而非EBCDIC。
$_ = “The HAL-9000 requires authorization to continue.”;
if(/HAL-[0-9]+/){
print “The string mentions some model of HAL computer.\n”;
}
有時,指出沒有被字元類包含的字元更加容易。字元類前使用符号^将取此字元類的補集。也就是說,[^def]]将比對上這三
個字元中之外的任意單個字元。[^n\-z]将比對上n, -, z 之外的任何字元。(連接配接符(-)前面使用反斜線的原因是,它在此字元
類中有特别的含義(表示字元的範圍:譯者注)。但/HAL-[0-9]+/中第一個連接配接符(-)前不需要反斜線,因為此時的連接配接符不
會被了解為有特殊的含義。)
7.3.1 字元類的簡寫
有一些字元類出現的非常頻繁,是以提供了其簡寫形式。例如,任何數字的類,[0-9],可以被簡寫為:\d。是以,HAL 這
Perl 語言入門(第四版)
[email protected] 96 / 201 9/21/2006
個例子可以被寫作/HAL-\d+/。
\w 被稱作“word’字元:[A-Za-z0-9_]。如果你的“words”由通常的字母,數字,下劃線組成,那你将非常喜歡它。通常認
為“word”由字母,連接配接符(-),撇号(')◆組成,我們希望能改變這種定義◆。是以使用它,請記住我們對“word”的定義,
字母,數字,下劃線組成。
◆至少,在英語中是這樣。在其它語言中,其words 由不同的符号組成。檢視perllocale 的幫助手冊了解更多的資訊。
◆當檢視ASCII 編碼的英國文本時,我們遇到單引号和撇号(')是相同字元的問題,是以很難說cat’是cat 和一個撇号( '),還是cat 後接單引
号。這可能是計算機還不能接管世界的一個原因。
當然,\w 不能比對單詞,而隻能比對單個字元。為了比對整個單詞,需要後接加号。模式/fred \w+ barney/将比對fred,空
格,一個“單詞(word)”,然後是空格和barney。是以,如果fred 和barney 之間有一個單詞◆,由單個空格分隔開,它将
能比對上。
◆我們将停止在word 上加引号;現在你已經知道其是由字母-數字-下劃線組成的。
你可能已經注意到在前一例中,如果能更加靈活的比對空白将很友善。\s 對于比對空白(whitespace)将非常友善。它等價
于[\f\t\n\r ],其含5 個空白字元:格式符(form-feed);制表符(tab),換行符,回車,以及空格符。同其它簡寫符号一樣,
\s 比對此類中的單個字元,如果使用\s*将比對任何個數的空白(包括沒有),或者\s+比對一個以上的空白(事實上,很少
見到單獨使用\s,而不使用任何的數量詞(*, +))。由于這些空白符看起來類似,是以可以使用這種簡寫形式,将它們統一處
理。
7.3.2 簡寫形式的補集
某些時候,你可能希望得到這三種簡寫形式的補集。如果那樣的話,你可以使用[^\d], [^\w], 和[^\s],其含義分别是,非數
字的字元,非word(記住我們對word 的定義)的字元,和非空白的字元。也可以使用它們對應的大寫形式:\D, \W, \S 來
完成。它們将比對它們對應的小寫形式不能比對上的字元。
這些簡寫形式可以在字元類中使用,或者在大的字元類中的中括号裡面使用。也就是說你可以使用/[\dA-Fa-f]+/來比對十六
進制(底為16)的數字,它将ABCDEF(或者其小寫形式)作為附加的數字(11 到15)。
另一個類字元[\d\D],它的意思是任何數字,和任何非數字,則意指任何字元。這是比對所有字元的一種通用方法,甚至包
括換行符,而點(.)比對除換行符以外的任何字元。而[^\d\D]則完全沒用,因為它比對既非數字也非非數字的字元,那什麼
也不是。