本網站更多資訊 |
簡介 |
正規表示式快速入門 |
正規表示式教學 |
替換字串教學 |
應用程式和語言 |
正規表示式範例 |
正規表示式參考 |
替換字串參考 |
書籍評論 |
可列印 PDF |
關於本網站 |
RSS 饋送和部落格 |
Perl 5.10、PCRE 4.0 和 Ruby 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 使用的所有語法。最新版本的 PHP、Delphi 和 R 也支援所有這些語法,因為它們的正規表示式函式是基於 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> 可以匹配 aa 和 bb,但不能匹配 ab 或 ba。在 Ruby 中,相同的正規表示式將匹配所有四個字串。本教學課程中討論的其他風格都不使用此語法作為反向參考。
遞迴進入擷取群組是比遞迴整個 regex 更靈活的 配對平衡結構 方式。我們可以將 regex 包在擷取群組中,遞迴進入擷取群組而非整個 regex,並在擷取群組外新增錨點。 \A(b(?:m|(?1))*e)\z 是用來檢查字串是否完全由正確平衡的結構組成的通用 regex。同樣地,b 是結構的開頭,m 是結構中間可能出現的內容,而 e 是結構結尾可能出現的內容。為了得到正確的結果,b、m 和 e 這三者不應有兩個以上可以配對相同的文字。你可以使用 原子群組 取代 非擷取群組 來提升效能:\A(b(?>m|(?1))*e)\z。
類似地,\Ao*(b(?:m|(?1))*eo*)+\z 和最佳化的 \Ao*+(b(?>m|(?1))*+eo*+)++\z 會配對一個字串,該字串只包含一個或多個正確平衡的結構序列,中間可能穿插其他文字。在此,o 是平衡結構外可能出現的內容。它通常會與 m 相同。 o 不應能配對與 b 或 e 相同的文字。
\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'$
| 快速開始 | 教學 | 工具和語言 | 範例 | 參考 | 書籍評論 |
| 簡介 | 目錄 | 特殊字元 | 不可列印字元 | Regex 引擎內部 | 字元類別 | 字元類別減法 | 字元類別交集 | 簡寫字元類別 | 點 | 錨點 | 字詞邊界 | 交替 | 可選項目 | 重複 | 分組和擷取 | 反向參照 | 反向參照,第 2 部分 | 命名群組 | 相對反向參照 | 分支重設群組 | 自由間距和註解 | Unicode | 模式修改器 | 原子群組 | 獨佔量詞 | 前瞻和後顧 | 前瞻和後顧,第 2 部分 | 將文字保持在比對之外 | 條件式 | 平衡群組 | 遞迴 | 子常式 | 無限遞迴 | 遞迴和量詞 | 遞迴和擷取 | 遞迴和反向參照 | 遞迴和回溯 | POSIX 方括號表示式 | 零長度比對 | 繼續比對 |
頁面網址:https://regular-expressions.dev.org.tw/subroutine.html
頁面最後更新:2019 年 11 月 22 日
網站最後更新:2024 年 3 月 15 日
版權所有 © 2003-2024 Jan Goyvaerts。保留所有權利。