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

命名擷取群組和反向參考

幾乎所有現代正規表示式引擎都支援 編號擷取群組編號反向參考。含有大量群組和反向參考的長正規表示式可能難以閱讀。它們可能會特別難以維護,因為在正規表示式中間新增或移除擷取群組會影響所有新增或移除群組之後的群組編號。

Python 的 re 模組率先提供了解決方案:命名擷取群組和命名回溯引用。(?P<name>group)group 的比對結果擷取到回溯引用「name」。name 必須是以字母開頭的英數字序列。group 可以是任何正規表示式。您可以使用命名回溯引用 (?P=name) 來參照群組的內容。問號、P、尖括號和等號都是語法的組成部分。儘管命名回溯引用的語法使用括號,但它只是一個不會進行任何擷取或分組的回溯引用。HTML 標籤範例可以寫成 <(?P<tag>[A-Z][A-Z0-9]*)\b[^>]*>.*?</(?P=tag)>

.NET 也支援命名擷取。Microsoft 的開發人員發明了自己的語法,而不是遵循 Python 開創並由 PCRE(當時唯一支援命名擷取的兩個正規表示式引擎)複製的語法。(?<name>group)(?'name'group)group 的比對結果擷取到回溯引用「name」。命名回溯引用是 \k<name>\k'name'。與 Python 相比,命名群組的語法中沒有 P。命名回溯引用的語法更類似於編號回溯引用,而不是 Python 使用的語法。您可以在名稱周圍使用單引號或尖括號。這在正規表示式中完全沒有差別。您可以交替使用這兩種樣式。在使用單引號來界定字串的程式語言中,建議使用尖括號的語法,而在將正規表示式新增到 XML 檔案時,建議使用單引號的語法,因為這可以將您必須執行的跳脫字元量減到最少,才能將正規表示式格式化為文字字串或 XML 內容。

由於 Python 和 .NET 引入了自己的語法,因此我們將這兩個變體稱為命名擷取和命名回溯引用的「Python 語法」和「.NET 語法」。如今,許多其他正規表示式風格都複製了此語法。

Perl 5.10 新增支援 Python 和 .NET 語法,用於命名擷取和反向參照。它還新增了命名反向參照的兩種語法變體:\k{one}\g{two}。Perl 中的五種命名反向參照語法之間沒有差異。所有語法都可以互換使用。在替換文字中,您可以內插變數 $+{name},以插入由命名擷取群組匹配的文字。

PCRE 7.2 和更新版本支援 Perl 5.10 支援的所有命名擷取和反向參照語法。舊版本的 PCRE 支援 Python 語法,儘管當時這並「不與 Perl 相容」。像 PHPDelphiR 等使用 PCRE 實作其 regex 支援的語言也支援所有這些語法。遺憾的是,PHP 或 R 都不支援替換文字中的命名參照。您必須對命名群組使用編號參照。PCRE 完全不支援搜尋和替換。

Java 7XRegExp 複製了 .NET 語法,但僅複製了使用尖括號的變體。Ruby 1.9 支援 .NET 語法的兩種變體。 JGsoft 風味 支援 Python 語法和 .NET 語法的兩種變體。

Boost 1.42 和更新版本支援使用 .NET 語法(帶尖括號或引號)的命名擷取群組,以及使用 Perl 5.10 的 \g 語法(帶大括號)的命名反向參照。Boost 1.47 還支援使用 .NET 的 \k 語法(帶尖括號和引號)的反向參照。Boost 1.47 允許這些變體相乘。Boost 1.47 允許使用 \g\k 以及大括號、尖括號或引號來指定命名和編號的反向參照。因此,Boost 1.47 和更新版本在基本的 \1 語法之上有六種反向參照語法變體。這使得 Boost 與 Ruby、PCRE、PHP、R 和 JGsoft 發生衝突,後者將帶尖括號或引號的 \g 視為 子常式呼叫

命名擷取群組的數字

不建議混合使用命名和編號的擷取群組,因為不同版本在群組編號方式上不一致。如果群組不需要名稱,請使用 (?:群組) 語法使其為非擷取。在 .NET 中,您可以透過設定 RegexOptions.ExplicitCapture 使所有未命名群組為非擷取。在 Delphi 中,請設定 roExplicitCapture。在 XRegExp 中,請使用 /n 旗標。 Perl 從 Perl 5.22 開始支援 /n。在 PCRE 中,請設定 PCRE_NO_AUTO_CAPTUREJGsoft 版本 和 .NET 支援 (?n) 模式修改子。如果您使所有未命名群組為非擷取,您可以略過此部分並省去一些麻煩。

大多數版本會從左至右計算命名和未命名擷取群組的開啟括號,並依序編號。將命名擷取群組新增至現有正規表示式仍會影響未命名群組的編號。然而,在 .NET 中,未命名擷取群組會先被編號,從左至右計算其開啟括號,略過所有命名群組。之後,命名群組會被編號,從左至右計算命名群組的開啟括號。

JGsoft 正規表示式引擎 在只有 Python 和 PCRE 使用 Python 語法,且只有 .NET 使用 .NET 語法時,複製了 Python 和 .NET 語法。因此,它也複製了 Python 和 .NET 的編號行為,以便針對 Python 和 .NET 的正規表示式能維持其行為。它會像 Python 一樣,將 Python 風格的命名群組與未命名群組一起編號。它會像 .NET 一樣,在之後將 .NET 風格的命名群組編號。即使您在同一個正規表示式中混合使用這兩種風格,這些規則仍然適用。

舉例來說,正規表示式 (a)(?P<x>b)(c)(?P<y>d) 會如預期般比對 abcd。如果您使用此正規表示式和替換 \1\2\3\4$1$2$3$4 (視版本而定) 進行搜尋和取代,您將會得到 abcd。所有四個群組都從左至右編號,從一到四。

使用 .NET 時,情況會稍微複雜一點。正規表示式 (a)(?<x>b)(c)(?<y>d) 仍會比對 abcd。不過,如果您使用 $1$2$3$4 進行搜尋並取代,您將會得到 acbd。首先,未命名群組 (a)(c) 取得數字 1 和 2。然後,命名群組「x」和「y」取得數字 3 和 4。

在所有其他複製 .NET 語法規則的版本中,正規表示式 (a)(?<x>b)(c)(?<y>d) 仍會比對 abcd。但在所有這些版本中,除了 JGsoft 版本外,取代字串 \1\2\3\4$1$2$3$4(視版本而定)會讓您得到 abcd。所有四個群組都從左到右編號。

在使用 JGsoft 版本的 PowerGREP 中,命名擷取群組扮演著特殊的角色。具有相同名稱的群組會在同一個 PowerGREP 動作中的所有正規表示式和取代文字之間共用。這允許在動作中某個部分由命名擷取群組擷取的文字在動作的後續部分中被參照。因此,PowerGREP 完全不允許對命名擷取群組進行編號參照。在正規表示式中混合使用命名群組和編號群組時,編號群組仍會按照 Python 和 .NET 規則進行編號,就像 JGsoft 版本始終執行的動作一樣。

具有相同名稱的多個群組

.NET 架構JGsoft 版本 允許正規表示式中的多個群組具有相同名稱。所有具有相同名稱的群組會共用儲存其比對文字的相同儲存空間。因此,對該名稱的反向參照會比對由具有該名稱且最近擷取到某個項目的群組比對的文字。在取代文字中對名稱的參照會插入由具有該名稱且最後一個擷取到某個項目的群組比對的文字。

PerlRuby 也允許群組具有相同名稱。但這些版本只使用障眼法,讓所有具有相同名稱的群組看起來像一個群組在作用。實際上,這些群組是分開的。在 Perl 中,反向參照會比對由具有該名稱且比對到某個項目的正規表示式中最左邊的群組擷取的文字。在 Ruby 中,反向參照會比對由具有該名稱的任何群組擷取的文字。回溯會讓 Ruby 嘗試所有群組。

因此在 Perl 和 Ruby 中,只有在正規表示式中不同的選項中使用相同名稱的群組才有意義,這樣才能讓具有該名稱的群組只有一個能擷取任何文字。然後對該群組的反向參照會合理地比對由群組擷取的文字。

例如,如果您想要比對「a」後接數字 0..5,或「b」後接數字 4..7,而且您只關心數字,您可以使用正規表示式 a(?<digit>[0-5])|b(?<digit>[4-7])。在這四種風味中,名為「digit」的群組將會提供與字母無關的比對數字 0..7。如果您想要這個比對後接 c 和完全相同的數字,您可以使用 (?:a(?<digit>[0-5])|b(?<digit>[4-7]))c\k<digit>

PCRE 預設不允許重複命名的群組。PCRE 6.7 及更新版本允許重複命名,只要您開啟該選項或使用 模式修改器 (?J)。但在 PCRE 8.36 之前,這並不太實用,因為反向參照總是指向正規表示式中第一個具有該名稱的擷取群組,而不管它是否參與比對。從 PCRE 8.36(因此 PHP 5.6.9 和 R 3.1.3)以及 PCRE2 開始,反向參照指向實際參與比對的第一個具有該名稱的群組。儘管 PCRE 和 Perl 以相反的方向處理重複群組,但如果您遵循建議,僅在不同的選項中使用具有相同名稱的群組,則最終結果相同。

Boost 允許重複命名的群組。在 Boost 1.47 之前,這並不實用,因為反向參照總是指向正規表示式中反向參照之前出現的最後一個具有該名稱的群組。在 Boost 1.47 及更新版本中,反向參照指向實際參與比對的第一個具有該名稱的群組,就像在 PCRE 8.36 及更新版本中一樣。

Python、Java 和 XRegExp 3 不允許多個群組使用相同名稱。這樣做會產生正規表示式編譯錯誤。XRegExp 2 允許重複命名,但無法正確處理。

在 Perl 5.10、PCRE 8.00、PHP 5.2.14 和 Boost 1.42(或這些更新版本)中,當您希望不同選項中的群組具有相同名稱時,最好使用 分支重設群組,例如 (?|a(?<digit>[0-5])|b(?<digit>[4-7]))c\k<digit>。使用這種特殊語法(以 (?| 而不是 (?: 開頭的群組),兩個名為「digit」的群組實際上是一個群組。然後,對該群組的反向參照始終在這些風味之間正確且一致地處理。(舊版本的 PCRE 和 PHP 可能支援分支重設群組,但無法正確處理分支重設群組中的重複名稱。)