讀古今文學網 > 精通正則表達式(第3版) > 發動引擎 >

發動引擎

Start Your Engines!

現在我們來看看,引擎的類比能為我們提供多大幫助。引擎的價值在於,有了它,你不需要花多少氣力就能從一個地方移動到另一個地方。駕駛員只需要放鬆或者聽聽音樂,發動機會完成餘下的事情。它的主要任務就是驅動車輪,而駕駛員沒必要關心它是如何工作的。是這樣嗎?

兩類引擎

Two Kinds of Engines

設想一下駕駛電動汽車的情形?電動汽車已經誕生很久了,但它們不像汽油發動機驅動的汽車那樣普及,因為電動汽車還不夠成熟。如果你有輛電動汽車,請記住別給它加油。如果你的汽車採用汽油發動機,請務必遠離煙火。電動機幾乎總是「能夠運行」,汽油機則需要多加保養。更換火花塞、空氣過濾器,或者換用不同品牌的汽油,有可能大大提升發動機的效率。當然,也可能降低汽油機的性能,或者導致發動機罷工。不同引擎的工作原理也有不同,但目的都是驅動車輪。不過,如果你想開車去某個地方,還得把好方向盤,當然,這是題外話。

新的標準

New Standards

讓我們看看添加一條新規範:加利福尼亞州的尾氣排放標準(注 1)。一些發動機達到了加州的嚴格排放標準,一些則沒有。這兩類發動機並沒有本質的不同,只是按標準劃分為兩類。這些標準規定了發動機尾氣排放的成分,而並沒有規定發動機應該怎樣做才能達標。所以,現在我們可以把之前的兩分法變為四分法:符合標準的電動機、不符合標準的電動機、符合標準的汽油機和不符合標準的汽油機。

回到原來的話題,我敢打賭,電動機不需要做多少改動就可以達標——標準只是「規定」尾氣的成分,而電動機幾乎沒有尾氣。相反,汽油機要達標可能就需要大的修改和更新。使用汽油發動機的駕駛員尤其需要注意汽油的型號——如果加錯了油,他們就惹上大麻煩了。

標準的作用

更嚴格的排放標準是個好玩意兒,但駕駛員也需要考慮更多,同時更加小心(至少對汽油車來說如此)。不過坦白說,新標準對大多數人沒有什麼影響,因為其他州不會施行加州的標準。

所以你知道,四種類型的發動機其實可以分為三類:兩類是汽油機,一類是電動機。雖然它們都是驅動車輪的,但你明白了其中的差異。你不知道的是,這堆複雜的玩意與正則表達式有什麼關係!其實這裡面的關係遠比你能想像的要複雜。

正則引擎的分類

Regex Engine Types

正則引擎主要可以分為基本不同的兩大類:一種是DFA(相當於之前說的電動機),另一種是NFA(相當於前面的汽油機)。我們很快就會知道DFA和NFA的具體含義,但是現在讀者只需要知道這兩個名字,就像「Bill」和「Ted」,「汽油機」和「電動機」一樣。

DFA 和NFA 都有很長的歷史,不過,正如汽油機一樣,NFA 的歷史更長一些。使用NFA的工具包括.NET、PHP、Ruby、Perl、Python、GNU Emacs、ed、sec、vi、grep的多數版本,甚至還有某些版本的egrep和awk。而採用DFA的工具主要有egrep、awk、lex和flex。也有些系統採用了混合引擎,它們會根據任務的不同選擇合適的引擎(甚至對同一表達式中的不同部分採用不同的引擎,以求得功能與速度之間的最佳平衡)。表4-1列出了少量常用的工具及其大多數版本使用的引擎。如果你最喜歡的工具沒有名列其中,可以參考下一頁的「測試引擎的類型」來找到答案。

表4-1:部分程序及其所使用的正則引擎

第3章已經講過,NFA和DFA都發展了二十多年,產生了許多不必要的變體,結果,現在的情況比較複雜。POSIX標準的出台,就是為了規範這種現象,POSIX標準不但清楚地規定了前一章中提到的引擎應該支持的元字符和特性,還明確規定了使用者期望由表達式獲得的準確結果。除開表面細節不談,DFA(也就是電動機)顯然已經符合新的標準,但是NFA風格的結果卻與此不一,所以NFA需要修改才能符合標準。這樣一來,正則引擎可以粗略地分為3類:

●DFA(符合或不符合POSIX標準的都屬此類)。

●傳統型NFA。

●POSIX NFA.

這裡提到的POSIX是匹配意義上的,也就是說,POSIX標準規定的某個正則表達式的應有行為(本章稍後部分將討論);而不是指POSIX標準引入的匹配特性。許多程序支持這些特性,但結果與POSIX規範不完全一致。

老式(功能極少的)程序,比如egrep、awk、lex之類,一般都是使用DFA引擎(電動機),所以,新的標準只是肯定了既有的情況,而沒有大的改變。但是也存在一些汽油機版本的此類程序,如果它們需要達到POSIX標準,就需要做些修改。通過了加州排放標準測試(POSIX NFA)的汽油機能夠產生符合標準的結果,但是這些必要的修改會增加保養的難度。以前,錯位的火花塞也能應付著使用,但現在根本就點不著火。以前還能「湊合」的汽油,現在會弄得發動機砰砰亂響。不過,一旦掌握其中的門道,發動機就能平穩安靜地運轉了。

幾句題外話

From the Department of Redundancy Department

現在,我請讀者回過頭去,重新思考關於引擎的故事。其中的每句話都涉及某些與正則表達式相關的事實。讀第二遍會引起許多思考。尤其是,為什麼說電動機(DFA引擎)只是「能夠運行」。什麼影響了汽油機(NFA)?使用NFA引擎時,應該如何調整才能獲得期望的結果?通過(排放)測試的POSIX DFA有什麼特別之處?現實中「熄火的引擎」又是什麼?

測試引擎的類型

Testing the Engine Type

工具所採用的引擎的類型,決定了引擎能夠支持的特性以及這些特性的用途。所以,通常情況下,我們只需要幾個測試用的表達式,就能判斷出程序所使用的引擎類型(畢竟,如果你不能分辨引擎的類型,這種分類就沒有意義)。現在,我並不期望讀者理解下面的這些測試原理,我只是提供一些測試表達式,即使讀者最喜歡使用的軟件沒有出現在表4-1之內,也可以判斷出引擎的類型,繼續閱讀本章的其他內容。

是否傳統型NFA

傳統型NFA是使用最廣泛的引擎,而且它很容易識別。首先,看看忽略優先量詞(☞141)是否得到支持?如果是,基本就能確定這是傳統型 NFA。我們將要看到,忽略優先量詞是DFA不支持的,在POSIX NFA中也沒有意義。為了確認這一點,只需要簡單地用正則表達式「nfa|nfa·not」來匹配字符串『nfa·not』,如果只有『nfa』匹配了,這就是傳統型NFA。如果整個『nfa not』都能匹配,則此引擎要麼是POSIX NFA,要麼是DFA。

DFA還是POSIX NFA

某些情況下,DFA 與 POSIX NFA 的區別是很明顯的。DFA 不支持捕獲型括號(capturing parentheses)和回溯(backreferences),這一點有助於判斷,不過,也存在同時使用兩種引擎的混合系統,在這種系統中,如果沒有使用捕獲型括號,就會使用DFA。

下面這個簡單的測試能說明很多問題,用「X(.+)+X」來匹配形如『=XX======================』的字符串,例如使用egrep命令:

echo=XX=========================================|egrep\'X(.+)+X\'

如果執行需要花很長時間,就是NFA(如果上一項測試顯示這不是傳統型NFA,那麼它肯定是POSIX NFA)。如果時間很短,就是DFA,或者是支持某些高級優化的NFA。如果顯示堆棧超溢(stack overflow),或者超時退出,那麼它是NFA引擎。