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

使用反向參照再次比對相同文字

反向參照會比對先前由擷取群組比對的相同文字。假設您想要比對一對開啟和關閉的 HTML 標籤,以及中間的文字。透過將開啟標籤放入反向參照中,我們可以重複使用標籤名稱作為關閉標籤。方法如下:<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1>。這個正規表示式只包含一對括號,用來擷取由[A-Z][A-Z0-9]*比對的字串。這是開啟的 HTML 標籤。(由於 HTML 標籤不分大小寫,因此這個正規表示式需要不分大小寫的比對。)反向參照\1(反斜線一)會參照第一個擷取群組。\1會比對與第一個擷取群組比對的完全相同的文字。/之前的\1是字面字元。這只是我們嘗試比對的關閉 HTML 標籤中的正斜線。

要找出特定反向引用的數字,請從左到右掃描正規表示式。計算所有編號擷取群組的開啟括號。第一個括號開始反向引用數字一,第二個數字二,依此類推。略過屬於其他語法(例如非擷取群組)的括號。這表示非擷取括號有另一個好處:您可以將它們插入正規表示式,而不會變更分配給反向引用的數字。這在修改複雜的正規表示式時非常有用。

您可以重複使用相同反向引用多次。 ([a-c])x\1x\1 相符 axaxabxbxbcxcxc

大多數正規表示式風格支援多達 99 個擷取群組和兩位數反向引用。因此,如果您的正規表示式有 99 個擷取群組,\99 是有效的反向引用。

深入了解正規表示式引擎

讓我們看看 regex 引擎如何將 regex <([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> 套用至字串 Testing <B><I>粗體斜體</I></B> 文字。regex 中的第一個 token 是字面 <。regex 引擎會在字串中移動,直到它能與字串中的第一個 < 相符。下一個 token 是 [A-Z]。regex 引擎也會注意到它現在位於第一對擷取括弧內。 [A-Z]B 相符。引擎會前進到 [A-Z0-9]>。此相符失敗。然而,由於 星號,這完全沒問題。字串中的位置仍停留在 >。由於 B 出現在前面,字詞邊界 \b 會與 > 相符。字詞邊界不會讓引擎在字串中前進。regex 中的位置會前進到 [^>]

此步驟會跨越第一對擷取括弧的右括弧。這會促使 regex 引擎將與其相符的內容儲存到第一個反向參照。在此情況下,會儲存 B

在儲存反向參照後,引擎會繼續進行相符嘗試。 [^>]> 不相符。同樣地,由於另一個星號,這不是問題。字串中的位置仍停留在 >,而 regex 中的位置會前進到 >。這些顯然相符。下一個 token 是點號,由一個懶惰星號重複。由於懶惰,regex 引擎最初會跳過此 token,並注意到如果 regex 的其餘部分失敗,它應該回溯。

引擎現在已抵達正規表示式中的第二個 <,以及字串中的第二個 <。它們相符。下一個代碼是 /。這與 I 不相符,引擎被迫回溯到句點。句點與字串中的第二個 < 相符。星號仍然是惰性的,因此引擎再次記錄可用的回溯位置,並前進到 <I。它們不相符,因此引擎再次回溯。

回溯持續進行,直到句點消耗掉 <I>粗體斜體。此時,< 與字串中的第三個 < 相符,而下一個代碼是 /,與 / 相符。下一個代碼是 \1。請注意,代碼是反向參照,而不是 B。引擎不會在正規表示式中替換反向參照。每次引擎抵達反向參照時,它都會讀取儲存的值。這表示如果引擎在第二次抵達 \1 之前回溯到第一對擷取括號之外,則會使用儲存在第一個反向參照中的新值。但這裡並未發生這種情況,因此它是 B。這無法與 I 相符,因此引擎再次回溯,句點消耗掉字串中的第三個 <

回溯再次持續進行,直到句點消耗掉 <I>粗體斜體</I>。此時,<< 相符,// 相符。引擎再次抵達 \1。反向參照仍包含 B\1B 相符。正規表示式中的最後一個代碼 >> 相符。已找到一個完整相符:<B><I>粗體斜體</I></B>

回溯到擷取群組

您可能對上面提到的字元界線 \b<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> 中感到疑惑。這是為了確保正規表示式不會比對錯誤配對的標籤,例如 <boo>bold</b>。您可能認為這不會發生,因為擷取群組比對 boo,這會導致 \1 嘗試比對相同的內容,然後失敗。這確實會發生。但接著正規表示式引擎會回溯。

我們來看看沒有字元界線的正規表示式 <([A-Z][A-Z0-9]*)[^>]*>.*?</\1>,並在 \1 第一次失敗時查看正規表示式引擎內部。首先,.*? 會持續擴充,直到到達字串的結尾,而 </\1>.*? 比對多一個字元時,每次都會失敗。

然後正規表示式引擎會回溯到擷取群組。[A-Z0-9]* 已比對出 oo,但也可以比對出 o 或什麼都沒有。在回溯時,[A-Z0-9]* 被迫放棄一個字元。正規表示式引擎會繼續執行,第二次離開擷取群組。由於 [A-Z][A-Z0-9]* 已比對出 bo,因此會儲存在擷取群組中,覆寫之前儲存的 boo[^>]* 比對出開啟標籤中的第二個 o>.*?</ 比對出 >bold</\1 再次失敗。

正規表示式引擎會再次執行所有相同的回溯,直到 [A-Z0-9]* 被迫放棄另一個字元,導致它比對出什麼都沒有,而 星號 允許這種情況。擷取群組現在只儲存 b[^>]* 現在比對出 oo>.*?</ 再次比對出 >bold<\1 現在成功,> 也是,並找到一個整體比對。但不是我們想要的。

解決此問題的方法有數種。一種方法是使用字詞邊界。當 [A-Z0-9]* 第一次回溯,將擷取群組縮小為 bo 時,\b 無法在 oo 之間進行配對。這會強制 [A-Z0-9]* 立即再次回溯。擷取群組縮小為 b,而字詞邊界無法在 bo 之間進行配對。沒有進一步的回溯位置,因此整個配對嘗試失敗。

我們需要字詞邊界的原因是,我們使用 [^>]* 來略過標籤中的所有屬性。如果您的配對標籤從未有任何屬性,您可以將其省略,並使用 <([A-Z][A-Z0-9]*)>.*?</\1>。每次 [A-Z0-9]* 回溯時,其後的 > 無法進行配對,快速結束配對嘗試。

如果您不希望正規表示式引擎回溯到擷取群組,可以使用原子群組。原子群組 教學部分有所有詳細資訊。

重複和反向參照

正如我在上述內部檢視中所提到的,正規表示式引擎並不會永久替換正規表示式中的反向參照。它會在每次需要使用時,使用儲存在反向參照中的最後一個比對。如果透過擷取括號找到新的比對,先前儲存的比對就會被覆寫。在 ([abc]+)([abc])+ 之間,有一個 明顯的差異。儘管兩者都能成功比對 cab,但第一個正規表示式會將 cab 放入第一個反向參照中,而第二個正規表示式只會儲存 b。這是因為在第二個正規表示式中,加號導致括號對重複三次。第一次,儲存 c。第二次,儲存 a,第三次,儲存 b。每次都會覆寫前一個值,因此 b 會保留下來。

這也表示 ([abc]+)=\1 會比對 cab=cab,而 ([abc])+=\1 則不會。原因是當引擎到達 \1 時,它會保留 b,而 b 無法比對 c。在檢視像這樣一個簡單的範例時,這一點很明顯,但它仍然是正規表示式中常見的困難原因。在使用反向參照時,務必仔細檢查您是否真的擷取到想要的內容。

實用的範例:檢查重複的字詞

在編輯文字時,重複的字詞(例如「the the」)很容易悄悄出現。在 文字編輯器 中使用正規表示式 \b(\w+)\s+\1\b,您可以輕鬆找到它們。若要刪除第二個字詞,只需輸入 \1 作為替換文字,然後按一下「取代」按鈕即可。

括號和反向參照不能用於字元類別內

括號不能用於 字元類別 內,至少不能用作元字元。當您在字元類別中放入括號時,它會被視為一個字面字元。因此,正規表示式 [(a)b] 會比對 ab()

反向參照也無法在字元類別中使用。在類似 (a)[\1b] 的正規表示式中,\1 是一個錯誤,或是一個不必要的轉義字元 1。在 JavaScript 中,它是一個 八進位轉義