閱讀429 返回首頁    go 技術社區[雲棲]


《正則表達式經典實例(第2版)》——2.17 根據條件匹配兩者之一

本節書摘來自異步社區《正則表達式經典實例(第2版)》一書中的第2章,第2.17節,作者: 【美】Jan Goyvaerts , Steven Levithan著,更多章節內容可以訪問雲棲社區“異步社區”公眾號查看

2.17 根據條件匹配兩者之一

問題描述
創建一個正則表達式,匹配一個由逗號分隔的單詞列表one、two和three。每個單詞可以在該列表中出現任意多次,但是每個單詞必須至少出現一次。

解決方案

\b(?:(?:(one)|(two)|(three))(?:,|\b)){3,}(?(1)|(?!))(?(2)|(?!))(?(3)|(?!))
正則選項:無
正則流派:.NET、PCRE、Perl、Python

Java、JavaScript和Ruby並不支持條件判斷。在這些(或者其他任何語言)中進行編程的時候,你可以使用不帶有條件判斷的正則表達式,再另外編寫一些其他的代碼來檢查其中的三個捕獲分組是否都匹配了相應的內容。

\b(?:(?:(one)|(two)|(three))(?:,|\b)){3,}
正則選項:無
正則流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby

討論
.NET、PCRE、Perl和Python支持使用編號捕獲分組的條件判斷(conditional)。‹(?(1)then|else)›是用來檢查第一個捕獲分組是否成功匹配的一個條件判斷。如果它匹配成功,正則引擎會嚐試去匹配‹then›。如果該捕獲分組到目前為止還沒有參與匹配嚐試,就會去嚐試匹配‹else›。

這裏的括號、問號和豎線都是屬於條件判斷語法的一部分。它們在這裏並不具有平時的含義。你可以在‹then›和‹else›部分中使用任意種類的正則表達式。唯一的限製是如果想要在其中一個部分之內使用選擇分支,就必須使用分組來把它們包到一起。因為在條件判斷中隻允許直接出現一條豎線。

如果願意,也可以省略掉‹then›或‹else›的部分。空的正則表達式總是會找到一個長度為0的匹配。這個實例所給的解決方案中使用了3個條件判斷,它們都包含空的‹then›部分。如果捕獲分組參與了匹配,那麼這個條件判斷隻會簡單地成功匹配。

一個空否定型順序環視‹(?!)›用在了‹else›部分。因為空的正則表達式總是會成功匹配,所以包含空正則表達式的否定型順序環視則總是會匹配失敗。因此,如果第一個捕獲分組沒有匹配到任何東西,條件判斷‹(?(1)|(?!))›必定會失敗。

通過把這三個必需的選擇分支放到自己的捕獲分組中,我們可以在正則表達式的結尾使用3個條件判斷來檢查是否所有的捕獲分組都捕獲到了內容。如果其中一個單詞沒有匹配,引用其捕獲分組的條件判斷就會執行“else”部分,該部分的空否定型順序環視就會導致條件判斷匹配失敗。因此隻要有一個單詞沒有匹配,整個正則式就沒有匹配成功。

要允許單詞以任意順序出現,並且出現任意次數,我們將所有單詞放在一個使用選擇分支的分組內,並使用量詞重複這個分組。因為我們有三個單詞,並且要求每個單詞至少匹配一次,所以我們知道這個分組至少要重複三次。.NET、Python和PCRE 6.7還支持命名捕獲分組的條件判斷。‹(?(name)then|else)›會檢查命名捕獲分組name是否參與了匹配嚐試。Perl 5.10及以後版本同樣支持命名條件判斷。但是Perl要求名字兩邊使用尖括號或單引號,如‹(?(< name >)then|else)›或‹(?('name')then|else)›。PCRE 7.0及以後版本也支持Perl的命名條件判斷語法,以及.NET和Python所采用的語法。

為了更好地理解條件判斷是如何工作的,我們來看一個正則表達式‹(a)?b(?(1)c|d)›。它本質上是與‹abc|bd›等價的一種更為複雜的形式。

如果目標字符串是由一個a開頭的,那麼它就會被捕獲到第一個捕獲分組中。如果不是,那麼第一個捕獲分組就沒有參與到匹配嚐試中。在該捕獲分組之後的問號是很重要的,因為這使得整個分組成為可選的。如果不存在a的話,該分組會重複0次,因此不會有機會捕獲任何內容。它不能捕獲一個長度為0的字符串。

如果你使用的是‹(a?)›,那該分組總是會參與到匹配嚐試中。而在該分組之後並不存在量詞,所以它會正好重複一次。該分組或者捕獲a,或者捕獲空串。

不管‹a›是否成功匹配,下一個記號是‹b›。然後是條件判斷。如果捕獲分組參與了匹配嚐試,即使它捕獲的是長度為0的字符串(在這裏是不可能的),都會嚐試匹配‹c›。如果沒有的話,那麼會嚐試匹配‹d›。

用一句話來描述,‹(a)?b(?(1)c|d)›或者匹配ab後跟著c,或者匹配b後跟著d。

在.NET、PCRE和Perl中(但是不包括Python),條件判斷中還可以使用環視。‹(?(?=if__)then|else)›首先會把‹(?=if )›當作一個正常的順序環視來進行測試。實例2.16中講解了它是如何執行的。如果環視匹配成功的話,那麼會接著嚐試匹配‹then›部分。如果沒有成功的話,那麼會嚐試匹配‹else›中的正則表達式。因為環視的長度為0,所以‹then›和‹else›中的正則表達式都會在目標文本中‹if›匹配成功或者失敗的同一位置處接著進行嚐試。

在條件判斷中,除了使用順序環視,也可以使用逆序環視。你還可以使用否定型的環視,雖然我們並不推薦這樣做,因為它會把“then”和“else”的含義反轉,從而造成不必要的混淆。

圖像說明文字使用環視的條件判斷也可以寫成不使用條件判斷的形式:‹(?=if)then| (?!if)else›。如果肯定型順序環視匹配成功,就會嚐試匹配‹then›部分。如果肯定型順序環視匹配失敗,那麼會嚐試匹配第二個選擇。接下來的否定型順序環視會做同樣的檢查。否定型順序環視在‹if›匹配失敗的時候會匹配成功,因為‹(?=if)›已經匹配失敗了,所以否定型順序環視肯定成功。因此,繼續嚐試匹配‹else›。把順序環視放到條件判斷中更節省時間,因為條件判斷隻需嚐試匹配‹if›一次。

最後更新:2017-06-02 19:35:55

  上一篇:go  《正則表達式經典實例(第2版)》——2.18 向正則表達式中添加注釋
  下一篇:go  《正則表達式經典實例(第2版)》——2.16 測試一個匹配,但不添加到整體匹配中