快速入門
教學
工具與語言
範例
參考
書籍評論
範例
正規表示式範例
數字範圍
浮點數
電子郵件地址
IP 位址
有效日期
數字日期轉文字
信用卡號碼
比對完整行
刪除重複行
程式設計
兩個相近的字詞
陷阱
災難性回溯
重複次數過多
阻斷服務
讓所有項目都為選用
重複擷取群組
混合 Unicode 與 8 位元
更多此網站內容
簡介
正規表示式快速入門
正規表示式教學
替換字串教學
應用程式與語言
正規表示式範例
正規表示式參考
替換字串參考
書籍評論
可列印 PDF
關於此網站
RSS Feed 與部落格
RegexBuddy—The best regular expression debugger!

重複擷取群組與擷取重複群組

在建立需要擷取群組來擷取相符文字部分的正規表示式時,一個常見的錯誤是重複擷取群組,而不是擷取重複群組。兩者的差異在於,重複擷取群組只會擷取最後一次反覆運算,而擷取另一個重複群組的群組則會擷取所有反覆運算。以下範例將說明兩者的差異。

假設您想要比對類似 !abc!!123! 的標籤。只有這兩種標籤是可能的,而且您想要擷取 abc123 來找出您取得哪個標籤。這很簡單:!(abc|123)! 就會達成目的。

現在讓我們假設標籤可以包含多個 abc123 的序列,例如 !abc123!!123abcabc!。快速且簡單的解決方案是 !(abc|123)+!。此正規表示式確實會比對這些標籤。然而,它不再符合我們將標籤標籤擷取到擷取群組中的需求。當此正規表示式比對 !abc123! 時,擷取群組只會儲存 123。當它比對 !123abcabc! 時,它只會儲存 abc

如果我們觀察正規表示式引擎如何將 !(abc|123)+! 套用到 !abc123!,這很容易理解。首先,! 比對 !。然後,引擎會進入擷取群組。它會記錄在引擎到達主旨字串中第一個和第二個字元之間的位置時,已進入擷取群組 #1。群組中的第一個標記是 abc,它比對 abc。找到比對,因此不會嘗試第二個替代方案。(引擎確實會儲存回溯位置,但不會在此範例中使用。)現在,引擎會離開擷取群組。它會記錄在引擎到達字串中第四個和第五個字元之間的位置時,已離開擷取群組 #1。

在離開群組後,引擎會注意到 加號。加號是貪婪的,因此會再次嘗試群組。引擎會再次進入群組,並記錄在字串中第四個和第五個字元之間的位置時,已進入擷取群組 #1。它也會記錄由於加號不是 獨佔的,因此可能會回溯。也就是說,如果無法再次比對群組,那也沒關係。在此回溯記錄中,正規表示式引擎也會儲存群組在群組前一次反覆運算中的進入和離開位置。

abc 無法比對 123,但 123 成功了。群組再次離開。會儲存字元 7 和 8 之間的離開位置。

加號允許再次反覆運算,因此引擎會再次嘗試。會儲存回溯資訊,並儲存群組新的進入位置。但現在,abc123 都無法比對 !。群組失敗,引擎會回溯。在回溯時,引擎會還原群組的擷取位置。也就是說,群組是在字元 4 和 5 之間進入,並在字元 7 和 8 之間離開。

引擎以 ! 進行,它與 ! 相符。找到一個整體相符。整體相符跨越整個主旨字串。擷取群組會擷取字元 5、6 和 7,或 123。當找到相符時,會捨棄回溯資訊,因此在事後無法得知群組先前曾反覆運算並與 abc 相符。(唯一的例外是 .NET 正規表示式引擎,它會在相符嘗試後保留擷取群組的回溯資訊。)

現在應該很明顯,在這個範例中擷取 abc123 的解決方案是:正規表示式引擎只能進入並離開群組一次。這表示加號應該在擷取群組內,而不是在外面。由於我們確實需要將兩個選項分組,因此我們需要在反覆運算的群組周圍放置第二個擷取群組:!((abc|123)+)!。當這個正規表示式與 !abc123! 相符時,擷取群組 #1 會儲存 abc123,而群組 #2 會儲存 123。由於我們對內部群組的相符沒有興趣,因此我們可以透過讓內部群組不進行擷取來最佳化這個正規表示式:!((?:abc|123)+)!