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

正規表示式子常式

Perl 5.10PCRE 4.0Ruby 1.9 支援正規表示式子常式呼叫。這些呼叫與正規表示式遞迴非常類似。子常式呼叫不會再次比對整個正規表示式,只會比對擷取群組內的正規表示式。您可以在正規表示式的任何位置呼叫任何擷取群組的子常式。如果您在子常式呼叫的群組內放置呼叫,您就會有一個遞迴擷取群組。

與正規表示式遞迴一樣,您可以使用各種語法來執行完全相同的事情。Perl 使用 (?1) 呼叫編號群組,(?+1) 呼叫下一個群組,(?-1) 呼叫前一個群組,以及 (?&name) 呼叫命名群組。您可以使用所有這些呼叫來參照同一個群組。 (?+1)(?'name'[abc])(?1)(?-1)(?&name) 比對長度為五個字母,且僅包含字母表前三個字母的字串。此正規表示式與 [abc](?'name'[abc])[abc][abc][abc] 完全相同。

PCRE 是第一個支援子程式呼叫的正規表示式引擎。 (?P<name>[abc])(?1)(?P>name) 匹配三個字母,例如 (?P<name>[abc])[abc][abc](?1) 是呼叫一個編號群組,而 (?P>name) 是呼叫一個命名群組。後者在 PCRE 手冊頁中稱為「Python 語法」。儘管此語法模仿 Python 用於 命名擷取群組 的語法,但這是 PCRE 的發明。Python 不支援子程式呼叫或遞迴。PCRE 7.2 新增 (?+1)(?-1) 以進行相對呼叫。PCRE 7.7 新增 Perl 5.10 和 Ruby 2.0 使用的所有語法。最新版本的 PHPDelphiR 也支援所有這些語法,因為它們的正規表示式函式是基於 PCRE。

Ruby 1.9 及之後版本使用的語法看起來更像反向參照。 \g<1>\g'1' 呼叫一個編號群組,\g<name>\g'name' 呼叫一個命名群組,而 \g<-1>\g'-1' 呼叫前一個群組。Ruby 2.0 新增 \g<+1>\g'+1' 以呼叫下一個群組。 \g<+1>(?<name>[abc])\g<1>\g<-1>\g<name>\g'+1'(?'name'[abc])\g'1'\g'-1'\g'name' 在 Ruby 2.0 中匹配與 Perl 範例在 Perl 中匹配的相同 5 個字母字串。帶有尖括號和引號的語法可以互換使用。

JGsoft V2 支援所有三組語法。稍後我們將看到,Perl、PCRE 和 Ruby 在處理子程式呼叫期間的 擷取反向參考回溯 方面存在差異。儘管它們複製了彼此的語法,但它們並沒有複製彼此的行為。然而,JGsoft V2 複製了它們的語法和行為。因此,JGsoft V2 有三種不同的正規表示式遞迴方式,您可以使用不同的語法進行選擇。但這些差異不會在這個頁面的基本範例中發揮作用。

Boost 1.42 從 Perl 複製了語法,但其實作卻因錯誤而受損,這些錯誤在版本 1.62 中仍未完全修復。最重要的是,除了 *{0,} 之外的量詞會導致子程式呼叫行為異常。這在 Boost 1.60 中已獲得部分修復,它也能正確處理 ?{0,1}

Boost 不支援 Ruby 子程式呼叫的語法。在 Boost 中,\g<1> 是反向參考(不是子程式呼叫),用於擷取群組 1。因此,([ab])\g<1> 可以匹配 aabb,但不能匹配 abba。在 Ruby 中,相同的正規表示式將匹配所有四個字串。本教學課程中討論的其他風格都不使用此語法作為反向參考。

匹配平衡結構

遞迴進入擷取群組是比遞迴整個 regex 更靈活的 配對平衡結構 方式。我們可以將 regex 包在擷取群組中,遞迴進入擷取群組而非整個 regex,並在擷取群組外新增錨點。 \A(b(?:m|(?1))*e)\z 是用來檢查字串是否完全由正確平衡的結構組成的通用 regex。同樣地,b 是結構的開頭,m 是結構中間可能出現的內容,而 e 是結構結尾可能出現的內容。為了得到正確的結果,bme 這三者不應有兩個以上可以配對相同的文字。你可以使用 原子群組 取代 非擷取群組 來提升效能:\A(b(?>m|(?1))*e)\z

類似地,\Ao*(b(?:m|(?1))*eo*)+\z 和最佳化的 \Ao*+(b(?>m|(?1))*+eo*+)++\z 會配對一個字串,該字串只包含一個或多個正確平衡的結構序列,中間可能穿插其他文字。在此,o 是平衡結構外可能出現的內容。它通常會與 m 相同。 o 不應能配對與 be 相同的文字。

\A(\((?>[^()]|(?1))*\))\z 符合一個字串,該字串僅包含一對平衡的括號,括號之間可能包含文字。 \A[^()]*+(\((?>[^()]|(?1))*+\)[^()]*+)++\z

比對同一結構超過一次

如果一個正規表示式需要在不同部分比對同一類型的結構(但不是完全相同的文字)超過一次,使用子常式呼叫會更簡短且精簡。假設您需要一個正規表示式來比對下列病歷資料

Name: John Doe
Born: 17-Jan-1964
Admitted: 30-Jul-2013
Released: 3-Aug-2013

此外,假設您需要準確比對日期格式,因此正規表示式可以篩選出有效的記錄,並將無效記錄留給人工檢查。在大部分正規表示式風味中,您可以輕鬆使用 自由間距語法 透過這個正規表示式來執行此動作

^Name:(.*)\r?\n
Born:(?:3[01]|[12][0-9]|[1-9])
       
-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
       
-(?:19|20)[0-9][0-9]\r?\n
Admitted:(?:3[01]|[12][0-9]|[1-9])
           
-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
           
-(?:19|20)[0-9][0-9]\r?\n
Released:(?:3[01]|[12][0-9]|[1-9])
           
-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
           
-(?:19|20)[0-9][0-9]$

透過子常式呼叫,您可以讓這個正規表示式更短、更容易閱讀和維護

^姓名:(.*)\r?\n
出生:(?'date'(?:3[01]|[12][0-9]|[1-9])
               
-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
               
-(?:19|20)[0-9][0-9])\r?\n
入院:\g'date'\r?\n
出院:\g'date'$

獨立子程式定義

在 Perl、PCRE 和 JGsoft V2 中,您可以使用特殊 DEFINE 群組進一步執行此步驟:(?(DEFINE)(?'subroutine'regex))。雖然這看起來像一個條件式,它參照不存在的群組 DEFINE,其中包含一個單一的名稱群組「subroutine」,但 DEFINE 群組是一種特殊語法。固定文字(?(DEFINE)開啟群組。括號關閉群組。這個特殊群組告訴 regex 引擎忽略其內容,除了解析它以取得已命名和已編號的擷取群組。您可以將任意數量的擷取群組放入 DEFINE 群組中。DEFINE 群組本身絕不匹配任何內容,而且絕不會無法匹配。它會被完全忽略。regex foo(?(DEFINE)(?'subroutine'skipped))bar匹配foobar。DEFINE 群組在此 regex 中完全多餘,因為沒有呼叫其內部的任何群組。

有了 DEFINE 群組,我們的 regex 會變成

(?(DEFINE)(?'date'(?:3[01]|[12][0-9]|[1-9])
                  
-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
                  
-(?:19|20)[0-9][0-9]))
^姓名:(.*)\r?\n
出生:(?P>date)\r?\n
入院:\ (?P>date)\r?\n
出院:\ (?P>date)$

子程式呼叫上的量詞

子程式呼叫上的量詞運作方式就像 遞迴上的量詞。呼叫會重複執行,直到滿足量詞為止。 ([abc])(?1){3} 會配對 abcb 和任何其他由前三個字母組合而成的四個字母組合。首先,群組配對一次,然後呼叫配對三次。此正規表示式等於 ([abc])[abc]{3}

群組上的量詞會被子程式呼叫忽略。 ([abc]){3}(?1) 也符合 abcb。首先,群組符合三次,因為它有量詞。然後子程式呼叫符合一次,因為它沒有量詞。 ([abc]){3}(?1){3} 符合六個字母,例如 abbcab,因為現在群組和呼叫都重複 3 次。這兩個正規表示式等於 ([abc]){3}[abc]([abc]){3}[abc]{3}

雖然 Ruby 不支援子程式定義群組,但它支援對重複零次的群組進行子程式呼叫。 (a){0}\g<1>{3} 符合 aaa。群組本身被略過,因為它重複零次。然後子程式呼叫根據其量詞符合三次。這也適用於 PCRE 7.7 及更新版本。它不適用於舊版本的 PCRE 或任何版本的 Perl,因為有錯誤。

病歷範例的 Ruby 版本可以進一步清理為

(?'date'(?:3[01]|[12][0-9]|[1-9])
        
-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
        
-(?:19|20)[0-9][0-9]){0}
^姓名:(.*)\r?\n
出生:\g'date'\r?\n
入院:\g'date'\r?\n
出院:\g'date'$