天天看點

《正規表達式經典執行個體(第2版)》——第 1 章 正規表達式簡介 1.1正規表達式的定義

本節書摘來自異步社群《正規表達式經典執行個體(第2版)》一書中的第1章,第1.1節,作者: 【美】jan goyvaerts , steven levithan著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

在你打開這本書的時候,很可能已經熱切地期望,要在代碼中插入本書中找到的那些包含諸多括号和問号的古怪字元串了。如果你已經準備好要“即查即用”,我們非常歡迎,第4~9章中會列出并講解了各種實用的正規表達式。

但是如果閱讀本書的前幾章,你将為未來節省大量的時間。例如,本章會向讀者介紹許多工具—其中一些工具是本書作者之一的jan所開發的,這些工具可以幫你事先測試和調試正規表達式,而不用等到把它們塞到代碼中之後再處理,那時候查找錯誤就非常困難了。而且開始這幾章還會展示使用正規表達式的不同特性和選項,幫助你輕松應對遇到的問題,并幫助你了解正規表達式,進而提高它們的性能,以及學習不同語言—甚至是你最喜歡的程式設計語言的不同版本之間—在處理正規表達式的時候出現的細微差别。

是以,我們在這些背景知識上花費了大量的精力,相信讀者在開始動手之前會閱讀這些内容,或是在使用正規表達式時遇到挫折,而想要鞏固你的了解的時候,會回頭來閱讀它們。

在本書的上下文中,正規表達式(regular expression)是一種可以在許多現代應用程式和程式設計語言中使用的特殊形式的文本模式。它們可以用來驗證輸入是否符合給定的文本模式;在一大段文本中查找比對該模式的文本;用其他文本來替換比對該模式的文本或者重新組織比對文本的片段;把一塊文本切分成一系列更小的文本,當然如果使用不當也可能搬起石頭砸自己的腳。本書會幫助你确切了解正在做的事情,進而避免可能會造成的災難性後果。

學會了使用正規表達式的技巧,就可以簡化許多程式設計和文本處理的任務,并且讓許多沒有正規表達式則根本無法實作的任務成為可能。從一個文檔中提取所有的電子郵件位址,至少需要幾十行,甚至是幾百行過程式代碼—這些代碼編寫起來費事,維護起來也麻煩。但是,如果采用了合适的正規表達式,如在執行個體4.1中所給的那樣,就隻需要很少的幾行甚至隻要一行代碼就可以了。

術語“正規表達式”的曆史

術語“正規表達式”來源于數學與計算機科學理論,它用來反映被稱為“正則性”的數學表達式特點。這樣一個表達式可以通過一個确定性有限自動機(dfa)用軟體來實作。一個dfa是一個不使用回溯的有限狀态機。

最早版本的grep工具所使用的文本模式是數學意義上的正規表達式。盡管名字看起來是一樣的,然而如今流行的perl風格的正規表達式已經完全不是數學意義上的正規表達式了。它們是采用非确定性的有限自動機(nfa)來實作的。你稍後就會學到和回溯有關的所有内容。關于這條說明,實幹的程式員需要記住的所有内容就是:象牙塔裡的一些計算機科學家,很不喜歡自己精心定義的術語被套用到現實世界中更為有用的技術。

但是,如果你試圖用一個正規表達式來做太多的事情,或者是在根本不适合的情形中非要使用正規表達式,就會明白為什麼會存在如下的說法1ff:

有些人每遇到一個問題,就會想“我知道怎麼做,用正規表達式就可以了。”于是他們就有兩個(而不是一個)問題需要解決了。

這些人所遇到的新問題指的就是他們并不會去閱讀使用者手冊,也就是現在你手裡的這本書。是以請繼續讀下去。正規表達式是一個強大的工具。如果你的工作涉及在計算機上編輯或是提取文本,牢固地掌握正規表達式就會為你少度過很多個不眠之夜。

1.1.1 衆多正規表達式流派

上一小節的标題确實表述得不那麼确切,我們并沒有定義正規表達式到底是什麼。我們也不可能給出定義。對于哪些文本模式是正規表達式,而哪些不是,并不存在正式的标準來給出嚴格精确的定義。可以想象得到,每種程式設計語言的設計人員,以及每款文本處理程式的開發人員,對于正規表達式應該是什麼樣子,都會有自己不同的想法。是以,我們就不得不面對這樣一大堆不同的正規表達式流派。

幸運的是,絕大多數設計人員與開發人員都比較懶惰。如果可以照搬别人已經做好的工作,為什麼非要自己建立一些全新的東西呢?正因為此,所有現代的正規表達式流派,包含本書要讨論的這些流派,其曆史都可以追溯到perl程式設計語言。我們把這些流派都稱作perl風格的正規表達式。它們的正規表達式文法都非常相似,而且在大多數情況下是互相相容的,但也不是完全如此。

作家也都很懶。在本書中,我們通常會使用regex或regexp來指代一個單個的正規表達式,而使用regexes來指代其複數形式2。

正則流派并不是和程式設計語言一一對應的。腳本語言傾向于擁有它們自己内置正規表達式流派,其他語言則會依賴于函數庫來提供正規表達式支援。有些函數庫是對多種語言可用的,而某些特定的語言則會選用一些不同的函數庫。

本章要講解的内容隻與正規表達式的流派有關,是以會徹底忽略任何與程式設計有關的考慮事項。從第3章開始,我們會給出一些代碼片段,是以你可以先跳到第3章的第一節,以了解你将會使用哪些流派。但是現在請先忽略所有與程式設計相關的内容。下面一小節中列出的工具将會通過“動手學習”的方式,讓你更友善地探索正規表達式的文法。

1.1.2 本書涵蓋的正則流派

在本書中,我們選擇了如今最為流行的正則流派。這些都是perl風格的正則流派。有些流派會比其他流派多一些特性。但是如果兩種流派擁有同一個特性的話,那麼它們通常都會使用相同的文法。當然也會有例外,當我們遇到這些煩人的不一緻情況時,在書中會加以提示。

所有這些正則流派都屬于目前正在活躍開發中的程式設計語言和函數庫的一部分。下面的流派清單會告訴你本書所用到的是哪些版本。在本書後面,如果所講解的正規表達式在所有流派中效果都一樣,那麼我們在提到該流派時就不會區分其版本。絕大多數場合都是這種情況。除了會影響到一些邊界情況的bug修複之外,正規表達式流派一般都不會改變,唯一例外是添加新的特性,原來認為是錯誤的文法,現在會被賦予新的含義。

.net

微軟公司的.net架構通過system.text.regularexpressions包,提供了一個功能全面的perl風格正則流派。本書涵蓋了.net 1.0~4.0版。而嚴格來講,.net正則流派隻存在兩個版本:1.0和2.0版。在 .net 1.1、3.0和3.5版本中,并沒有對正則類進行任何修改。.net 4.0的正則類有一些新方法,但沒有改變正則文法。

任何.net程式設計語言,包括c#、vb.net、delphi for .net,甚至包括cobol.net,都可以完整使用.net正則流派。如果一個使用.net開發的程式提供了正規表達式支援,即使它号稱使用的是“perl正規表達式”,你也可以非常确定它使用的就是.net正則流派。很長時間裡,令人大跌眼鏡的一個例外則是visual studio(vs)自身。直到visual studio 2010,vs內建開發環境(ide)中依然使用的是它從一開始就在用的一個老版本的正則流派,而這個實作根本就不是perl風格的。本書寫作時正處于測試版的visual studio 113ff,最終也在ide中使用了.net正則流派。

java

java 4是通過java.util.regex包提供内置正規表達式支援的第一個java版本。這個包很快就超越了各種第三方java正則庫。除了它是标準的和内置的之外,它還支援了功能完整的perl風格正則流派,其卓越的性能甚至可以媲美使用c語言編寫的正則程式。本書會講解java 4、5、6和java 7中的java.util.regex包。如果你在過去幾年中用過java語言開發的軟體的話,那麼其支援的正規表達式很可能是java流派。

javascript

在本書中,我們使用javascript這個術語指代在ecma-262标準的第3版和第5版中定義的正規表達式流派。這個标準定義了ecmascript程式設計語言,而這個語言更廣為人知的是它在不同網頁浏覽器中的javascript與jscript實作。internet explorer(自5.5版)、firefox、opera與safari都實作了ecma-262的第3版或第5版。盡管在正規表達式功能方面,javascript 3與javascript 5的差別很小。然而,所有的浏覽器都擁有各種與标準不同的邊界情形bug。我們會在必要的地方指出這些問題。

如果一個網站允許使用正規表達式進行查找或者過濾,并且不用等待網站伺服器的響應,那麼它使用的就是javascript正則流派,這是唯一的跨浏覽器用戶端正則流派。即使是微軟的vbscript與adobe公司的actionscript 3使用的也是它,不過actionscript 3添加了一些額外特性。

xregexp

xregexp是steven levithan開發的開源javascript庫,可以從<code>http://xregexp.com</code>下載下傳它。xregexp擴充了javascript的正規表達式文法,并且消除了一些web浏覽器間的不一緻。本書中的執行個體在用到标準javascript不支援的正規表達式特性時,會附加使用xregexp的解決方案。如果一個解決方案的正則流派中列出了xregexp,就意味着這個解決方案在javascript代碼中使用xregexp庫時可以正常工作,而不使用xregexp的标準javascript代碼無法正常工作。如果一個解決方案的正則流派中列出了javascript,則意味着無論javascript代碼中是否使用xregexp,都可以正常工作。

本書涵蓋了xregexp 2.0版。所有執行個體都假設讀者使用的是包含全部xregexp unicode特性的xregexp-all.js。

pcre

pcre是由philip hazel開發的“與perl相容的正規表達式” (perl-compatible regular expressions)的c語言函數庫。這個開源代碼庫可以從<code>http://www.pcre.org</code>下載下傳。本書涉及的pcre版本包括第4版到第8版。

雖然pcre号稱是與perl相容的,而且與本書中的其他流派相比,它也是與perl相容性最好的,但是實際上它也隻能稱為是“perl風格”的正則流派。有些特性,比如unicode支援,會與perl稍微有些不同,并且不能像在perl中所允許的那樣,把perl代碼混合到你的正規表達式之中。

因為它采用了開源許可,并且擁有穩定可靠的實作,是以pcre被應用到了許多程式設計語言和程式中。它被内置到php中,并且被包裝到了許多個delphi的元件中。如果一個應用程式号稱它支援“與perl相容”的正規表達式,而卻沒有具體列出它實際使用的正則流派,那麼就很可能是pcre。

perl

perl對于正規表達式的内置支援是正規表達式今天得以流行的主要原因。本書會涉及perl 5.6、5.8、5.10、5.12和5.14版。上述每個版本都為perl正規表達式文法添加了新的特性。當本書中标明某正則式在特定perl版本中工作時,那該正則式可在該特定版本及本書所涉及的所有更新版本中正常工作。

許多應用程式和正則庫都号稱它們使用的是perl或者與perl相容的正規表達式,而事實上它們僅僅是使用了perl風格的正規表達式。它們使用與perl相似的正則文法,但是所支援正則特性集并不重疊。最有可能的情形是,它們使用的是這一特性集中的正規表達式流派之一,而這些流派都是屬于perl風格的。

python

python通過它的re子產品來支援正規表達式。本書會講到python 2.4至3.2版。python 2.4、2.5、2.6和2.7版間的差別非常小。python 3.0增強了python處理正規表達式中unicode的能力。python 3.1和3.2則沒有與正則相關的改進。

ruby

與perl語言類似,ruby的正規表達式支援是ruby語言自身的一部分。本書會涉及ruby 1.8和ruby 1.9。ruby 1.8的預設編譯會使用由ruby源代碼直接提供的正規表達式流派。而ruby 1.9的預設編譯則會使用oniguruma正規表達式庫。ruby 1.8也可以編譯使用oniguruma,而ruby 1.9也可以編譯使用舊版本的ruby正則流派。在本書中,我們會把原生ruby流派稱為ruby 1.8,而把oniguruma流派稱為ruby 1.9。

如果想測試一下你的站點使用的是哪個ruby正則流派,可以嘗試用一下正規表達式‹a++›。ruby 1.8會說這個正規表達式是非法的,因為它并不支援占有量詞(possessive quantifier),而它在ruby 1.9中則會比對一個或者多個字元a組成的字元串。

oniguruma庫設計為與ruby 1.8向後相容,它隻是在其上添加了新的功能,而不會破壞已有的正規表達式。該實作甚至原樣保留了大家認為應該修改的功能,例如:它依然使用‹ (?m) ›表示“點号比對換行符”,即使其他正規表達式流派使用的都是‹ (?s) ›。