本網站的更多內容 |
簡介 |
正規表示式快速入門 |
正規表示式教學 |
替換字串教學 |
應用程式和語言 |
正規表示式範例 |
正規表示式參考 |
替換字串參考 |
書籍評論 |
可列印 PDF |
關於本網站 |
RSS 饋送和部落格 |
反向參照會比對先前由擷取群組比對的相同文字。假設您想要比對一對開啟和關閉的 HTML 標籤,以及中間的文字。透過將開啟標籤放入反向參照中,我們可以重複使用標籤名稱作為關閉標籤。方法如下:<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1>。這個正規表示式只包含一對括號,用來擷取由[A-Z][A-Z0-9]*比對的字串。這是開啟的 HTML 標籤。(由於 HTML 標籤不分大小寫,因此這個正規表示式需要不分大小寫的比對。)反向參照\1(反斜線一)會參照第一個擷取群組。\1會比對與第一個擷取群組比對的完全相同的文字。/之前的\1是字面字元。這只是我們嘗試比對的關閉 HTML 標籤中的正斜線。
要找出特定反向引用的數字,請從左到右掃描正規表示式。計算所有編號擷取群組的開啟括號。第一個括號開始反向引用數字一,第二個數字二,依此類推。略過屬於其他語法(例如非擷取群組)的括號。這表示非擷取括號有另一個好處:您可以將它們插入正規表示式,而不會變更分配給反向引用的數字。這在修改複雜的正規表示式時非常有用。
您可以重複使用相同反向引用多次。 ([a-c])x\1x\1 相符 axaxa、bxbxb 和 cxcxc。
大多數正規表示式風格支援多達 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。\1 與 B 相符。正規表示式中的最後一個代碼 > 與 > 相符。已找到一個完整相符:<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 無法在 o 和 o 之間進行配對。這會強制 [A-Z0-9]* 立即再次回溯。擷取群組縮小為 b,而字詞邊界無法在 b 和 o 之間進行配對。沒有進一步的回溯位置,因此整個配對嘗試失敗。
我們需要字詞邊界的原因是,我們使用 [^>]* 來略過標籤中的所有屬性。如果您的配對標籤從未有任何屬性,您可以將其省略,並使用 <([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] 會比對 a、b、( 和 )。
反向參照也無法在字元類別中使用。在類似 (a)[\1b] 的正規表示式中,\1 是一個錯誤,或是一個不必要的轉義字元 1。在 JavaScript 中,它是一個 八進位轉義。
| 快速入門 | 教學 | 工具與語言 | 範例 | 參考 | 書籍評論 |
| 簡介 | 目錄 | 特殊字元 | 非列印字元 | 正規表示式引擎內部 | 字元類別 | 字元類別減法 | 字元類別交集 | 簡寫字元類別 | 點 | 錨點 | 字詞邊界 | 交替 | 可選項目 | 重複 | 群組與擷取 | 反向參照 | 反向參照,第 2 部分 | 命名群組 | 相對反向參照 | 分支重設群組 | 自由間距與註解 | Unicode | 模式修改器 | 原子群組 | 獨佔量詞 | 前瞻與後顧 | 前瞻與後顧,第 2 部分 | 將文字保留在比對之外 | 條件式 | 平衡群組 | 遞迴 | 子常式 | 無限遞迴 | 遞迴與量詞 | 遞迴與擷取 | 遞迴與反向參照 | 遞迴與回溯 | POSIX 方括號表示式 | 零長度比對 | 繼續比對 |
頁面網址:https://regular-expressions.dev.org.tw/backref.html
頁面最後更新:2021 年 8 月 12 日
網站最後更新:2024 年 3 月 15 日
版權所有 © 2003-2024 Jan Goyvaerts。保留所有權利。