本網站的更多資訊 |
簡介 |
正規表示式快速開始 |
正規表示式教學 |
替換字串教學 |
應用程式和語言 |
正規表示式範例 |
正規表示式參考 |
替換字串參考 |
書籍評論 |
可列印 PDF |
關於本網站 |
RSS 摘要和部落格 |
我們看到 錨點、字詞邊界 和 環顧 會在位置比對,而不是比對字元。這表示當正規表示式只包含一個或多個錨點、字詞邊界或環顧時,可能會產生零長度比對。根據情況,這可能非常有用或不受歡迎。
例如在電子郵件中,通常會在引用的訊息的每一行開頭加上「大於」符號和空格。在 VB.NET 中,我們可以使用 Dim Quoted As String = Regex.Replace(Original, "^", "> ", RegexOptions.Multiline) 輕鬆做到這一點。我們使用多行模式,因此正規表示式 ^ 會在引用的訊息開頭和每個換行符號後進行比對。Regex.Replace 方法會從字串中移除正規表示式比對結果,並插入取代字串(大於符號和空格)。由於比對結果不包含任何字元,因此不會刪除任何內容。不過,比對結果會包含一個起始位置。取代字串會插入在那裡,就像我們想要的。
使用 ^\d*$ 來測試使用者是否輸入數字會產生不良結果。它會導致腳本將空字串視為有效的輸入。讓我們看看為什麼。
空字串中只有一個「字元」位置:字串後的空白。正規表示式中的第一個代碼是 ^。它會比對字串後空白前的字元位置,因為字串前空白在前面。下一個代碼是 \d*。星號的其中一個作用是讓 \d 在此情況下變成選用的。引擎會嘗試將 \d 與字串後的空白進行比對。這會失敗。但星號會將 \d 的失敗轉換為零長度的成功。引擎會繼續進行下一個正規表示式代碼,而不會推進字串中的位置。因此,引擎會到達 $ 和字串後的空白。這些會比對。在這個時候,整個正規表示式已比對空字串,而引擎會回報成功。
解決方案是使用正規表示式 ^\d+$,並使用適當的量詞來要求輸入至少一個數字。如果你總是確保正規表示式找不到零長度的比對結果(除了特殊情況,例如比對每一行的開頭或結尾),那麼你可以省去閱讀本主題剩餘部分的麻煩。
並非所有風味都支援零長度比對。在 Delphi XE5 及更早版本中的 TRegEx 類別總是會略過零長度比對。TPerlRegEx 類別在 XE5 及更早版本中預設也會略過,但允許您透過 State 屬性變更這個設定。在 Delphi XE6 及更新版本中,TRegEx 永遠不會略過零長度比對,而 TPerlRegEx 預設不會略過,但仍允許您透過 State 屬性略過。 PCRE 預設會尋找零長度比對,但如果您設定 PCRE_NOTEMPTY,則可以略過。
如果正規表示式可以在字串中的任何位置找到零長度比對,它就會這麼做。正規表示式 \d* 比對零個或多個數字。如果主旨字串不包含任何數字,則這個正規表示式會在字串中的每個位置找到一個零長度比對。它在字串 abc 中找到 4 個比對,分別在三個字母的前面各一個,以及在字串的結尾處一個。
當一個正規表示式可以在任何位置找到零長度比對,以及某些非零長度比對時,事情就變得棘手了。假設我們有正規表示式 \d*|x,主旨字串 x1,而且正規表示式引擎允許零長度比對。當我們遍歷所有比對時,我們會得到哪些比對,以及得到多少個比對?答案取決於正規表示式引擎在零長度比對後如何前進。無論如何,答案都很棘手。
第一次比對嘗試從字串的開頭開始。 \d 無法比對 x。但 * 使得 \d 可選。第一個選項在字串的開頭找到一個零長度比對。到目前為止,所有允許零長度比對的正規表示式引擎都執行相同的動作。
現在正規表示式引擎處於一個棘手的狀況。我們要求它遍歷整個字串,以找到所有不重疊的正規表示式比對。第一次比對在字串的開頭結束,而第一次比對嘗試也是從那裡開始。正規表示式引擎需要一種方法,來避免陷入一個無限迴圈,永遠在字串的開頭找到同一個零長度比對。
最簡單的解決方案是,如果前一個比對是零長度,則從前一個比對的結尾處往後一個字元開始下一次比對嘗試。在這種情況下,第二次比對嘗試從字串中 x 和 1 之間的位置開始。 \d 比對 1。已到達字串的結尾。量詞 * 滿足於重複一次。 1 被傳回作為整體比對。
另一個由 Perl 使用的解決方案,是不論前一個比對是否為零長度,都從前一個比對的結尾開始下一個比對嘗試。如果前一個比對為零長度,引擎會記錄下來,因為它不允許在同一個位置進行零長度比對。因此,Perl 也會從字串的開頭開始進行第二次比對嘗試。第一個選項再次找到一個零長度比對。但這不是一個有效的比對,因此引擎會回溯正規表示式。\d* 被迫放棄其零長度比對。現在嘗試正規表示式中的第二個選項。x 比對 x,並找到第二個比對。第三個比對嘗試從字串中 x 之後的字元開始。第一個選項比對 1,並找到第三個比對。
但正規表示式引擎尚未完成。在比對 x 之後,它會再進行一次比對嘗試,從字串結尾開始。在此,\d* 也找到一個零長度比對。因此,根據引擎在零長度比對後如何前進,它會找到三個或四個比對。
一個例外是 JGsoft 引擎。JGsoft 引擎會在零長度比對後前進一個字元,就像大多數引擎一樣。但它有一個額外的規則,會略過前一個比對結束位置的零長度比對,因此您永遠不會在非零長度比對的正後方有一個零長度比對。在我們的範例中,JGsoft 引擎只找到兩個比對:字串開頭的零長度比對,以及 1。
Python 3.6 及更早版本會在零長度比對後前進。用於搜尋和取代的 gsub() 函數會略過前一個非零長度比對結束位置的零長度比對,但 finditer() 函數會傳回這些比對。因此,Python 中的搜尋和取代會產生與 Just Great Software 應用程式相同結果,但列出所有比對會在字串結尾加入零長度比對。
Python 3.7 改變了這一切。它會像 Perl 一樣處理零長度比對。gsub() 現在會取代與其他比對相鄰的零長度比對。這表示可以在 Python 3.7 和更早版本之間找到零長度比對的正規表示式不相容。
PCRE 8.00 及更新版本和 PCRE2 會透過回溯來處理零長度比對,就像 Perl 一樣。它們不再像 PCRE 7.9 一樣在零長度比對後前進一個字元。
R 和 PHP 中的 regexp 函數是以 PCRE 為基礎,因此它們會透過回溯來避免卡在零長度比對上,就像 PCRE 一樣。但 R 中用於搜尋和取代的 gsub() 函數也會略過前一個非零長度比對結束位置的零長度比對,就像 Python 3.6 及更早版本的 gsub() 一樣。R 中的其他 regexp 函數和 PHP 中的所有函數都允許零長度比對緊鄰非零長度比對,就像 PCRE 本身一樣。
例如 $ 本身的正規表示式可以在字串結尾找到零長度比對。如果您要查詢引擎的字元位置,它會傳回字串長度(如果字串索引從 0 開始)或長度+1(如果字串索引從 1 開始),具體取決於您的程式語言。如果您要查詢引擎的比對長度,它會傳回 0。
您必須注意的是,String[Regex.MatchPosition] 可能會導致存取違規或區段錯誤,因為 MatchPosition 可能指向字串後的空隙。如果字串中的最後一個字元是換行符,這也可能發生在 ^ 和 ^$ 在 多行模式 中。
| 快速入門 | 教學 | 工具和語言 | 範例 | 參考 | 書籍評論 |
| 簡介 | 目錄 | 特殊字元 | 不可列印字元 | Regex 引擎內部 | 字元類別 | 字元類別減法 | 字元類別交集 | 簡寫字元類別 | 點 | 錨點 | 字詞邊界 | 交替 | 可選項目 | 重複 | 群組和擷取 | 反向參照 | 反向參照,第 2 部分 | 命名群組 | 相對反向參照 | 分支重設群組 | 自由間距和註解 | Unicode | 模式修改器 | 原子群組 | 獨佔量詞 | 前瞻和後顧 | 前瞻和後顧,第 2 部分 | 將文字保留在比對結果之外 | 條件式 | 平衡群組 | 遞迴 | 子常式 | 無限遞迴 | 遞迴和量詞 | 遞迴和擷取 | 遞迴和反向參照 | 遞迴和回溯 | POSIX 方括號表示式 | 零長度比對 | 繼續比對 |
頁面網址:https://regular-expressions.dev.org.tw/zerolength.html
頁面最後更新時間:2019 年 11 月 22 日
網站最後更新時間:2024 年 3 月 15 日
版權所有 © 2003-2024 Jan Goyvaerts。保留所有權利。