天天看點

第七章正規表達式

第七章正規表達式

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]則完全沒用,因為它比對既非數字也非非數字的字元,那什麼

也不是。