快速入門
教學
工具和語言
範例
參考
書籍評論
正規表示式教學
簡介
目錄
特殊字元
不可列印字元
正規表示式引擎內部
字元類別
字元類別減法
字元類別交集
簡寫字元類別
錨定
字詞邊界
交替
可選項目
重複
群組和擷取
反向參照
反向參照,第 2 部分
命名群組
相對反向參照
分支重設群組
自由間隔和註解
Unicode
模式修改器
原子群組
獨佔量詞
前瞻和後顧
環顧,第 2 部分
將文字保留在比對之外
條件
平衡群組
遞迴
子常式
無限遞迴
遞迴和量詞
遞迴和擷取
遞迴和反向參照
遞迴和回溯
POSIX 方括號表示式
零長度比對
繼續比對
本網站的更多資訊
簡介
正規表示式快速入門
正規表示式教學
替換字串教學
應用程式和語言
正規表示式範例
正規表示式參考
替換字串參考
書籍評論
可列印 PDF
關於本網站
RSS 饋送和部落格
RegexBuddy—Better than a regular expression tutorial!

正規表示式中的如果-然後-否則條件

特殊結構 (?ifthen|else) 允許您建立條件正規表示式。如果 if 部分評估為真,正規表示式引擎將嘗試比對 then 部分。否則,將嘗試 else 部分。語法包含一對括號。開頭方括號後面必須接一個問號,緊接著 if 部分,緊接著 then 部分。這個部分後面可以接一個直線和 else 部分。您可以省略 else 部分,以及直線。

對於 if 部分,您可以使用 先行斷言和後行斷言 結構。使用正向先行斷言,語法會變成 (?(?=regex)then|else)。由於先行斷言有自己的括號,因此 ifthen 部分會清楚地分開。

請記住,斷言結構不會使用任何字元。如果您使用先行斷言作為 if 部分,則 regex 引擎會嘗試在 if 嘗試的位置比對 thenelse 部分(取決於先行斷言的結果)。

或者,您可以在 if 部分檢查 擷取群組 是否已參與比對。將擷取群組的數字放在括號中,並將其用作 if 部分。請注意,儘管對反向參照進行條件檢查的語法與擷取群組中的數字相同,但不會建立擷取群組。數字和括號是 if-then-else 語法的一部分,以 (? 開頭。

對於 thenelse,您可以使用任何正規表示式。如果您要使用 交替,則必須使用 括號thenelse 分組在一起,例如 (?(?=condition)(then1|then2|then3)|(else1|else2|else3))。否則,不需要在 thenelse 部分周圍使用括號。

檢視 regex 引擎內部

regex (a)?b(?(1)c|d) 包含選用的擷取群組 (a)?、文字 b 和條件 (?(1)c|d),用於測試擷取群組。此 regex 比對 bdabc。它不比對 bc,但會比對 abd 中的 bd。讓我們看看這個正規表示式如何對這四個主旨字串中的每個字串運作。

應用於 bd 時,a 無法配對。由於包含 a 的擷取群組是選用的,因此引擎會從主旨字串的開頭繼續執行 b。由於整個群組是選用的,因此群組並未參與配對。任何後續的 反向參照,例如 \1,都將失敗。請注意,(a)?(a?) 非常不同。在前一個正規表示式中,如果 a 失敗,擷取群組將不會參與配對,而且對群組的反向參照將會失敗。在後一個群組中,擷取群組總是會參與配對,擷取 a 或什麼都不擷取。對參與配對且未擷取任何內容的擷取群組的反向參照總是會成功。評估此類群組的條件式會執行「then」部分。簡而言之:如果您要在條件式中使用對群組的參照,請使用 (a)?,而不是 (a?)

繼續我們的正規表示式,b 配對 b。正規表示式引擎現在評估條件式。第一個擷取群組根本未參與配對,因此會嘗試「else」部分或 dd 配對 d,並找到整體配對。

轉到我們的第二個主旨字串 abca 配對 a,並由擷取群組擷取。隨後,b 配對 b。正規表示式引擎再次評估條件式。擷取群組參與了配對,因此會嘗試「then」部分或 cc 配對 c,並找到整體配對。

我們的第三個主旨 bc 沒有以 a 開頭,因此擷取群組不會參與配對嘗試,就像我們在第一個主旨字串中看到的一樣。b 仍然配對 b,而引擎會繼續進行條件式。第一個擷取群組根本未參與配對,因此會嘗試「else」部分或 dd 不配對 c,而且在字串開頭的配對嘗試失敗。引擎會從字串中的第二個字元開始再次嘗試,但會失敗,因為 b 不配對 c

第四個主題 abd 是最有趣的。如同在第二個字串中,擷取群組會擷取 ab 匹配。擷取群組參與匹配,因此會嘗試「then」部分或 cc 未能匹配 d,且匹配嘗試失敗。請注意,此時並未嘗試「else」部分。擷取群組參與匹配,因此只會使用「then」部分。然而,正規表示式引擎尚未完成。它會從一開始重新啟動正規表示式,在主題字串中向前移動一個字元。

從字串中的第二個字元開始,a 未能匹配 b。擷取群組不會參與從字串中的第二個字元開始的第二次匹配嘗試。正規表示式引擎會移動到可選群組之外,並嘗試匹配 b。正規表示式引擎現在會到達正規表示式中的條件式,以及主題字串中的第三個字元。第一個擷取群組未參與目前的匹配嘗試,因此會嘗試「else」部分或 dd 匹配 d,且會找到整體匹配 bd

如果您想要避免最後的匹配結果,您需要使用 錨點^(a)?b(?(1)c|d)$ 在最後一個主題字串中找不到任何匹配。插入符號未能匹配字串中的第二和第三個字元之前。

命名和相對條件式

條件式受到 JGsoft 引擎PerlPCREPython.NET 的支援。 Ruby 從 2.0 版開始支援條件式。基於 PCRE 的正規表示式功能的語言,例如 DelphiPHPR 也支援條件式。

所有這些版本也支援 命名擷取群組。您可以使用擷取群組的名稱,而不是其數字作為 if 測試。語法在正規表示式版本之間略有不同。在 Python、.NET 和 JGsoft 應用程式中,您只需在括號中指定群組的名稱。 (?<test>a)?b(?(test)c|d) 是使用命名擷取的前一節中的正規表示式。在 Perl 或 Ruby 中,您必須在群組名稱周圍加上尖括號或引號,並將其放在條件的括號之間:(?<test>a)?b(?(<test>)c|d)(?'test'a)?b(?('test')c|d)。PCRE 支援所有三個變體。

PCRE 7.2 和更新版本以及 JGsoft V2 也支援相對條件式。語法與參照編號擷取群組的條件式相同,在群組編號之前加上正號或負號。然後,條件式會計算從開啟條件式的 (?( 開始,向左(減號)或向右(正號)的開啟括號。 (a)?b(?(-1)c|d) 是撰寫上述正規表示式的另一種方式。好處是,如果您在正規表示式的開頭或結尾新增擷取群組,這個正規表示式不會中斷。

Python 支援使用編號或命名擷取群組的條件式。Python 不支援使用環顧的條件式,即使 Python 在條件式之外支援環顧。您不能使用類似 (?(?=regex)then|else) 的條件式,您可以替換為兩個相反的環顧:(?=regex)then|(?!regex)else

條件式參考不存在的擷取群組

BoostRuby 將參考不存在的擷取群組的條件式視為錯誤。本教學課程中討論的所有其他版本的最新版本並非如此。他們僅讓此類條件式嘗試「else」部分。不過,少數版本改變了想法。Python 3.4 及更早版本和 PCRE 7.6 及更早版本(因此 PHP 5.2.5 及更早版本)過去將它們視為錯誤。

範例:擷取電子郵件標頭

正規表示式 ^((From|To)|Subject)((?(2)\w+@\w+\.[a-z]+|.+)) 從電子郵件訊息中擷取 From、To 和 Subject 標頭。標頭名稱會擷取到第一個反向參考中。如果標頭是 From 或 To 標頭,也會擷取到第二個反向參考中。

此模式的第二部分是 if-then-else 條件式 (?(2)\w+@\w+\.[a-z]+|.+))if 部分檢查第二個擷取群組是否參與到目前為止的比對。如果標頭是 From 或 To 標頭,則會參與。在這種情況下,條件式的 then 部分 \w+@\w+\.[a-z]+ 會嘗試 比對電子郵件地址。為了讓範例簡單,我們使用過於簡單的正規表示式來比對電子郵件地址,而且我們不會嘗試比對通常也是 From 或 To 標頭一部分的顯示名稱。

如果第二個擷取群組到目前為止沒有參與比對,則會嘗試 else 部分 .+。這只會比對該行的剩餘部分,允許任何測試主旨。

最後,我們在條件式周圍放置一對額外的括號。這會將條件式比對到的電子郵件標頭內容擷取到第三個反向參照中。條件式本身不會擷取任何內容。在實作這個正規表示式時,第一個擷取群組會儲存標頭名稱(「From」、「To」或「Subject」),而第三個擷取群組會儲存標頭的值。

您可以嘗試透過在「else」部分中放入另一個條件式來比對更多標頭。例如,^((From|To)|(Date)|Subject)((?(2)\w+@\w+\.[a-z]+|(?(3)mm/dd/yyyy|.+))) 會比對「From」、「To」、「Date」或「Subject」,並使用正規表示式 mm/dd/yyyy 來檢查 日期是否有效。顯然地,日期驗證正規表示式只是一個用來讓範例簡單的佔位符。標頭會擷取到第一個群組中,而其驗證後的內容會擷取到第四個群組中。

正如你所見,使用條件式的正規表示式很快就會變得難以處理。我建議你僅在你的工具只允許你使用一個正規表示式時才使用它們。在編程時,你最好使用正規表示式 ^(||日期|主旨)(.+) 來擷取一個標頭及其未驗證的內容。在你的原始碼中,檢查第一個擷取群組中傳回的標頭名稱,然後使用第二個正規表示式來驗證第一個正規表示式的第二個擷取群組中傳回的標頭內容。雖然你必須撰寫幾行額外的程式碼,但此程式碼將更容易理解和維護。如果你預先編譯所有正規表示式,使用多個正規表示式將與塞滿條件式的單一大型正規表示式一樣快,甚至更快。