全面分析Linux正則表達式(四)
Linux正則表達式的子模式(subpatterns),逆向引用(Back references)上篇文章應(yīng)經(jīng)詳細介紹了,還有一部分內(nèi)容介紹了量詞(quantifiers),量詞匹配時的greedy與ungreedy。這里加已詳細介紹。
Linux正則表達式:命名子模式(named subpattern)
一些工具(例如Python)可以為逆向引用命名,從而定義出命名子模式。在Python中對正則表達式的使用是以函數(shù)或方法調(diào)用的格式,語法與這里舉的例子有較大差別。有興趣的朋友可以參看一下自己使用的工具來看看是否支持命名子模式。
重復(fù)(Repetition)和量詞(quantifiers)
在前面介紹逆向引用的部分里我們已經(jīng)接觸到了量詞(quantifiers)的概念,例如前面的例子/([abc]){3}/表示三個連續(xù)的字符,每個字符都必然是 “abc”這三個字符中的一個。在這個模式里,{3}就屬于量詞。它表示一個模式需要重復(fù)匹配(repetition)的數(shù)目。
量詞可以放在下面這些項目之后:
●單個字符(有可能是被轉(zhuǎn)義的單個字符,如xhh)
●“.”元字符
● 由方括號表示的字符類
● 逆向引用
●由小括號定義的子模式(除非它是個斷言,我們會在以后介紹)
最通用的量詞使用形式是用花括號括起的兩個由逗號分隔的數(shù)字,如這樣的格式{min,max},例如,/z{2,4}/ 可以匹配 "zz", "zzz", 或者 "zzzz",花括號中的***值以及前面的逗號可以省略,例如/d{3,}/可以匹配三個以上的數(shù)字,數(shù)字的數(shù)目沒有上限,而/d{3}/(注意,沒有逗號)則精確的匹配3個數(shù)字。當花括號出現(xiàn)在不允許量詞的位置或者語法與前面提到的不符時,這里它僅僅代表花括號字符本身而不再具有特殊的含義。例如{,6}不是量詞,它僅僅代表這四個字符本身的含義。
為了方便,三個最常用的量詞有它們的單字符縮寫形式,它們的的含義如下表:
* 相當于 {0,}
+ 相當于 {1,}
? 相當于 {0,1}
這也是以上三個元字符做為量詞使用含義。
在使用量詞特別是沒有上限限制的量詞時,應(yīng)該特別注意不要構(gòu)成無限循環(huán),例如/(a?)*/,在有的正則表達式工具里。這會形成一個編譯錯,不過有的工具卻允許這種結(jié)構(gòu),但不能保證各種工具都可以很好的處理這種結(jié)構(gòu)。
量詞匹配的“greedy”與“ungreedy”
在使用帶量詞的模式時,我們常會發(fā)現(xiàn)對同一模式而言,同一個目標字符串可以有多種匹配方式。例如/d{0,1}d/,可以匹配兩個或三個十進制數(shù)字,如果目標字符串是123,當量詞取下限0里,它匹配“12”,當量詞取上限1里,它匹配“123”整個字符。這兩種匹配結(jié)果都是正確的,如果我們?nèi)∷淖幽J?(d{0,1}d)/,則匹配的結(jié)果1到底是“12”還是“123”?
實際的運行結(jié)果一般會是后者,因為默認情況下,大多數(shù)正則表達式工具的匹配是按“greedy”原則匹配的?!癵reedy”單詞的中的含義是“貪吃的, 貪婪的”的意思,它的行為也如此單詞的含義,所謂greedy匹配意指在量詞限制范圍內(nèi),只要能保持后續(xù)模式的匹配,匹配總是盡可能的重復(fù)下去,直到不匹配的情況發(fā)生為止。為便于理解,我們看下面這個簡單的例子。
/(d{1,5})d/匹配“12345”這個字符串,這個模式表示在1到5個數(shù)字后面跟上一個數(shù)字,量詞范圍從1到5,當它的值在1-4時,整個模式都是匹配的,1的值可以是“1”,“12”,“123”,“1234”,而在greedy匹配的情況下,它取匹配時的量詞***值,因此最終匹配的結(jié)果是”1234”。
在大多數(shù)情況下,這就是我們想要的結(jié)果,但情況并不總這樣。例如,我們希望用下面這個模式提取出c語言的注釋部分(在c語言中,注釋語句放在字符串/*和*/之間)。我們使用的正則表達式是/*.**/,但匹配的結(jié)果卻完全和需要的不同。當正則表達式解析到“/*”這后的“.*”時,因為“.”可以代表任意字符,這也包含了其后需要匹配的“*/”,在量詞的作用下,這個匹配將一直進行下去,超過下一個“*”/直到文本的結(jié)束,這顯然不是我們需要的結(jié)果。
為了完成如上例我們想要的那種匹配,正則表達式引入了ungreedy匹配方法,與greedy匹配相反,在滿足整個模式匹配的前提下,它總是取最小的量詞數(shù)目結(jié)果。Ungreedy匹配用在量詞后面加上問號“?”來表示。例如在匹配C語言的注釋時,我們把正則表達式寫成如下形式:/*.*?*/,在量詞“*”后加上問號就可以達成想要的結(jié)果。還有前面那個例子用/(d{1,5})d/匹配“12345”這個字符串,如果改寫為ungreedy模式向這樣/(d{1,5}?)d/,、1的值將為1。
上面的解釋也許有些不準確,量詞后的問號的作用實際上是反轉(zhuǎn)當前的正則表達式的greedy與ungreedy行為。你可以通過模式修正符“U”將正則表達式設(shè)成ungreedy模式然后在模式中通過量詞后的問號將之反轉(zhuǎn)為greedy。
Linux正則表達式:一次性子模式(Once-only subpatterns)
關(guān)于量詞的另一個有趣的話題是一次性子模式(Once-only subpatterns)。要理解它的概念需要先了解一下含有量詞的正則表達式的匹配過程。我們這里舉個例子。
現(xiàn)在,讓我們用模式/d+foo/來匹配字符串“123456bar”,當然,它的結(jié)果是沒有匹配。但正則表達式引擎是如何工作的呢?它先分析前面的d+,這代表一個以上的數(shù)字,然后檢查目標字符串的對應(yīng)位置的***個字符“1”,符合模式,然后根據(jù)量詞重復(fù)這個模式對字符串進行匹配直到“123456”始終符合“d+”模式,接著它在目標字符串中遇到字符“b”無法與“d+”匹配,于是查看“d+”的后續(xù)模式“foo”,與目標字符串的后續(xù)部分“bar”無法匹配,這時,有趣的事情出現(xiàn)了,解釋引擎會對前面已經(jīng)解析過的“d+”模式進行回溯,將量詞數(shù)目減少一,看剩余部分能否匹配,此時“d+”的值改為“12345”,然后解釋引擎看目標字符串剩余的部分“6bar”能否與剩余的模式“foo”相匹配,如果不行,就把量詞數(shù)再減一,直到達到最小的量詞限制,如果仍無法匹配,則表明目標字符串無法匹配,返回?zé)o法匹配的結(jié)果。
現(xiàn)在,我們就可以來接觸一次性子模式了。所謂一次性子模式就是定義在正則表達式解析時不需要上述回溯過程的子模式。它用左圓括號后面的問號和小于號來表示,向這樣(?>)。如果將上面提到的例子改為一次性子模式,可以這樣書寫:
/(?>d)+foo/,這時,當解析器遇到后面不匹配的bar時,會立即返回不匹配的結(jié)果,而不會進行前面提到的回溯過程。
需要了解的是,一次性子模式屬于非捕獲子模式,它的匹配結(jié)果不能被逆向引用。
當一個沒有設(shè)定重復(fù)上限的子模式中包含了同樣沒有設(shè)定重復(fù)上限的模式時,使用一次性子模式是唯一可以避免讓你的程序陷入長時間等待的方法。例如你用“/(D+|<d+>)*[!?]/”這個模式去匹配一長串的a字符,向這樣“aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa”,在返回最終無匹配的結(jié)果前,你會等待很長的一段時間。這個模式表示一串非數(shù)字字符或者用尖括號括著的一串數(shù)字后跟隨著嘆號或者問號,把這段字符串分成兩個重復(fù)的部分會有很多種分法,而無論是子模式本身還是子模式之內(nèi)的量詞的各可能值都要經(jīng)過逐一測試,這將使最終的運算量達到一個很大的程度。這樣,你將在電腦前等待相當長的時間才會看到結(jié)果。而如果用一次性子模式來改寫剛才的模式,改成這樣/ ((?>D+)|<d+>)*[!?]/,你就可以很快得到運算的結(jié)果。
【編輯推薦】