深入淺出之正規表示式(一)

tengrid發表於2009-05-18

http://blog.chinaunix.net/u/9465/showart.php?id=433805

深入淺出之正規表示式(一)



前言:
       半年前我對正規表示式產生了興趣,在網上查詢過不少資料,看過不少的教程,最後在使用一個正規表示式工具RegexBuddy時發現他的教程寫的非常好,可以說是我目前見過最好的正規表示式教程。於是一直想把他翻譯過來。這個願望直到這個五一長假才得以實現,結果就有了這篇文章。關於本文的名字,使用“深入淺出”似乎已經太俗。但是通讀原文以後,覺得只有用“深入淺出”才能準確的表達出該教程給我的感受,所以也就不能免俗了。
       本文是Jan Goyvaerts為RegexBuddy寫的教程的譯文,版權歸原作者所有,歡迎轉載。但是為了尊重原作者和譯者的勞動,請註明出處!謝謝!

 

1.      什麼是正規表示式

基本說來,正規表示式是一種用來描述一定數量文字的模式。Regex代表Regular Express。本文將用<>來表示一段具體的正規表示式。

一段文字就是最基本的模式,簡單的匹配相同的文字。

 

2.      不同的正規表示式引擎

正規表示式引擎是一種可以處理正規表示式的軟體。通常,引擎是更大的應用程式的一部分。在軟體世界,不同的正規表示式並不互相相容。本教程會集中討論Perl 5 型別的引擎,因為這種引擎是應用最廣泛的引擎。同時我們也會提到一些和其他引擎的區別。許多近代的引擎都很類似,但不完全一樣。例如.NET正則庫,JDK正則包。

 

3.      文字元號

最基本的正規表示式由單個文字元號組成。如<>,它將匹配字串中第一次出現的字元“a”。如對字串“Jack is a boy”。“J”後的“a”將被匹配。而第二個“a”將不會被匹配。

正規表示式也可以匹配第二個“a”,這必須是你告訴正規表示式引擎從第一次匹配的地方開始搜尋。在文字編輯器中,你可以使用“查詢下一個”。在程式語言中,會有一個函式可以使你從前一次匹配的位置開始繼續向後搜尋。

類似的,<>會匹配“About cats and dogs”中的“cat”。這等於是告訴正規表示式引擎,找到一個<>,緊跟一個<
>,再跟一個<>。

要注意,正規表示式引擎預設是大小寫敏感的。除非你告訴引擎忽略大小寫,否則<>不會匹配“Cat”。

 

·        特殊字元

對於文字字元,有11個字元被保留作特殊用途。他們是:

[ ] \ ^ $ . | ? * + ( )

這些特殊字元也被稱作元字元。

如果你想在正規表示式中將這些字元用作文字字元,你需要用反斜槓“\”對其進行換碼 (escape)。例如你想匹配“1+1=2”,正確的表示式為<<1\+1=2>>.

需要注意的是,<<1+1=2>>也是有效的正規表示式。但它不會匹配“1+1=2”,而會匹配“123+111=234”中的“111=2”。因為“+”在這裡表示特殊含義(重複1次到多次)。

在程式語言中,要注意,一些特殊的字元會先被編譯器處理,然後再傳遞給正則引擎。因此正規表示式<<1\+2=2>>在C++中要寫成“1\\+1=2”。為了匹配“C:\temp”,你要用正規表示式<>。而在C++中,正規表示式則變成了“C:\\\\temp”。

 

·        不可顯示字元

可以使用特殊字元序列來代表某些不可顯示字元:

<>代表Tab(0x09)

<>代表回車符(0x0D)

<>代表換行符(0x0A)

要注意的是Windows中文字檔案使用“\r\n”來結束一行而Unix使用“\n”。

 

4.      正規表示式引擎的內部工作機制

知道正規表示式引擎是如何工作的有助於你很快理解為何某個正規表示式不像你期望的那樣工作。

有兩種型別的引擎:文字導向(text-directed)的引擎和正則導向(regex-directed)的引擎。Jeffrey Friedl把他們稱作DFA和NFA引擎。本文談到的是正則導向的引擎。這是因為一些非常有用的特性,如“惰性”量詞(lazy quantifiers)和反向引用(backreferences),只能在正則導向的引擎中實現。所以毫不意外這種引擎是目前最流行的引擎。

你可以輕易分辨出所使用的引擎是文字導向還是正則導向。如果反向引用或“惰性”量詞被實現,則可以肯定你使用的引擎是正則導向的。你可以作如下測試:將正規表示式<>應用到字串“regex not”。如果匹配的結果是regex,則引擎是正則導向的。如果結果是regex not,則是文字導向的。因為正則導向的引擎是“猴急”的,它會很急切的進行表功,報告它找到的第一個匹配 。

 

·        正則導向的引擎總是返回最左邊的匹配

這是需要你理解的很重要的一點:即使以後有可能發現一個“更好”的匹配,正則導向的引擎也總是返回最左邊的匹配。

當把<>應用到“He captured a catfish for his cat”,引擎先比較<>和“H”,結果失敗了。於是引擎再比較<>和“e”,也失敗了。直到第四個字元,<>匹配了“c”。<
>匹配了第五個字元。到第六個字元<>沒能匹配“p”,也失敗了。引擎再繼續從第五個字元重新檢查匹配性。直到第十五個字元開始,<>匹配上了“catfish”中的“cat”,正規表示式引擎急切的返回第一個匹配的結果,而不會再繼續查詢是否有其他更好的匹配。

 

 

5.      字符集

字符集是由一對方括號“[]”括起來的字符集合。使用字符集,你可以告訴正規表示式引擎僅僅匹配多個字元中的一個。如果你想匹配一個“a”或一個“e”,使用<>。你可以使用<>匹配gray或grey。這在你不確定你要搜尋的字元是採用美國英語還是英國英語時特別有用。相反,<>將不會匹配graay或graey。字符集中的字元順序並沒有什麼關係,結果都是相同的。

你可以使用連字元“-”定義一個字元範圍作為字符集。<>匹配0到9之間的單個數字。你可以使用不止一個範圍。<>匹配單個的十六進位制數字,並且大小寫不敏感。你也可以結合範圍定義與單個字元定義。<>匹配一個十六進位制數字或字母X。再次強調一下,字元和範圍定義的先後順序對結果沒有影響。

 

·        字符集的一些應用

查詢一個可能有拼寫錯誤的單詞,比如<> 或 <

  • >。

    查詢程式語言的識別符號,<>。(*表示重複0或多次)

    查詢C風格的十六進位制數<<0[xX][A-Fa-f0-9]+>>。(+表示重複一次或多次)

     

    ·        取反字符集

    A-Za-z][A-Za-z0-9]*>匹配沒有屬性的HTML標籤,“”是文字元號。第一個字符集匹配一個字母,第二個字符集匹配一個字母或數字。

    我們似乎也可以用。但是它會匹配<1>。但是這個正規表示式在你知道你要搜尋的字串不包含類似的無效標籤時還是足夠有效的。

     

    ·        限制性重複

    許多現代的正規表示式實現,都允許你定義對一個字元重複多少次。詞法是:{min,max}。min和max都是非負整數。如果逗號有而max被忽略了,則max沒有限制。如果逗號和max都被忽略了,則重複min次。

    因此{0,}和*一樣,{1,}和+ 的作用一樣。

    你可以用<>匹配1000~9999之間的數字(“\b”表示單詞邊界)。<>匹配一個在100~99999之間的數字。

     

    ·        注意貪婪性

    假設你想用一個正規表示式匹配一個HTML標籤。你知道輸入將會是一個有效的HTML檔案,因此正規表示式不需要排除那些無效的標籤。所以如果是在兩個尖括號之間的內容,就應該是一個HTML標籤。

    許多正規表示式的新手會首先想到用正規表示式<< <.> >>,他們會很驚訝的發現,對於測試字串,“This is a first test”,你可能期望會返回,然後繼續進行匹配的時候,返回

    但事實是不會。正規表示式將會匹配“first”。很顯然這不是我們想要的結果。原因在於“+”是貪婪的。也就是說,“+”會導致正規表示式引擎試圖儘可能的重複前導字元。只有當這種重複會引起整個正規表示式匹配失敗的情況下,引擎會進行回溯。也就是說,它會放棄最後一次的“重複”,然後處理正規表示式餘下的部分。

    和“+”類似,“?*”的重複也是貪婪的。

     

    ·        深入正規表示式引擎內部

    讓我們來看看正則引擎如何匹配前面的例子。第一個記號是“”。到目前為止,“<.>first test”。引擎會試圖將“>”與換行符進行匹配,結果失敗了。於是引擎進行回溯。結果是現在“<.>first tes”。於是引擎將“>”與“t”進行匹配。顯然還是會失敗。這個過程繼續,直到“<.>first”與“>”匹配。於是引擎找到了一個匹配“first”。記住,正則導向的引擎是“急切的”,所以它會急著報告它找到的第一個匹配。而不是繼續回溯,即使可能會有更好的匹配,例如“”。所以我們可以看到,由於“+”的貪婪性,使得正規表示式引擎返回了一個最左邊的最長的匹配。

     

    ·        用懶惰性取代貪婪性

    一個用於修正以上問題的可能方案是用“+”的惰性代替貪婪性。你可以在“+”後面緊跟一個問號“?”來達到這一點。“*”,“{}”和“?”表示的重複也可以用這個方案。因此在上面的例子中我們可以使用“<.>”。讓我們再來看看正規表示式引擎的處理過程。

    再一次,正規表示式記號“”匹配“M”,結果失敗了。引擎會進行回溯,和上一個例子不同,因為是惰性重複,所以引擎是擴充套件惰性重複而不是減少,於是“<.>”。這次得到了一個成功匹配。引擎於是報告“”是一個成功的匹配。整個過程大致如此。

     

    ·        惰性擴充套件的一個替代方案

    我們還有一個更好的替代方案。可以用一個貪婪重複與一個取反字符集:“]+>”。之所以說這是一個更好的方案在於使用惰性重複時,引擎會在找到一個成功匹配前對每一個字元進行回溯。而使用取反字符集則不需要進行回溯。

    最後要記住的是,本教程僅僅談到的是正則導向的引擎。文字導向的引擎是不回溯的。但是同時他們也不支援惰性重複操作。

     

    7.      使用“.”匹配幾乎任意字元

    在正規表示式中,“.”是最常用的符號之一。不幸的是,它也是最容易被誤用的符號之一。

    “.”匹配一個單個的字元而不用關心被匹配的字元是什麼。唯一的例外是新行符。在本教程中談到的引擎,預設情況下都是不匹配新行符的。因此在預設情況下,“.”等於是
  • 來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/94384/viewspace-600338/,如需轉載,請註明出處,否則將追究法律責任。

    相關文章