閱讀344 返回首頁    go 京東網上商城


《正則表達式經典實例(第2版)》——2.11 捕獲和命名匹配子串

本節書摘來自異步社區《正則表達式經典實例(第2版)》一書中的第2章,第2.11節,作者: 【美】Jan Goyvaerts , Steven Levithan著,更多章節內容可以訪問雲棲社區“異步社區”公眾號查看

2.11 捕獲和命名匹配子串

問題描述
創建一個正則表達式,匹配yyyy-mm-dd格式的任意日期,並且分別捕獲年、月和日。這樣做的目的是為了在處理匹配的代碼中可以更容易處理這些分別捕獲的值。為了更好地實現這個目標,向每個捕獲的文本添加描述性的名稱:“year”、“month”和“day”。

再創建一個正則表達式來匹配yyyy-mm-dd格式的“神奇”日期。一個神奇日期指的是年份後2位與月份和日期都是相同的數字。例如,2008-08-08就是一個神奇日期。把神奇數字捕獲下來(在這個例子中是08),並給它打上標簽“magic”。

你可以假設目標文本中的所有日期都是合法的。正則表達式不必考慮去掉像9999-99-99這樣的非法數據,因為它們根本不可能出現在目標文本中。

解決方案
命名捕獲

\b(?<year>\d\d\d\d)-(?<month>\d\d)-(?<day>\d\d)\b
正則選項:無
正則流派:.NET、Java 7、XRegExp、PCRE 7、Perl 5.10、Ruby 1.9
\b(?'year'\d\d\d\d)-(?'month'\d\d)-(?'day'\d\d)\b
正則選項:無
正則流派:.NET、PCRE 7、Perl 5.10、Ruby 1.9
\b(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)\b
正則選項:無
正則流派:PCRE 4 或更新版本、Perl 5.10、Python

命名反向引用

\b\d\d(?<magic>\d\d)-\k<magic>-\k<magic>\b
正則選項:無
正則流派:.NET、Java 7、XRegExp、PCRE 7、Perl 5.10、Ruby 1.9
\b\d\d(?'magic'\d\d)-\k'magic'-\k'magic'\b
正則選項:無
正則流派:.NET、PCRE 7、Perl 5.10、Ruby 1.9
\b\d\d(?P<magic>\d\d)-(?P=magic)-(?P=magic)\b
正則選項:無
正則流派:PCRE 4 或更新版本、Perl 5.10、Python

討論
命名捕獲
實例2.9和實例2.10講解了捕獲分組和反向引用。更加準確地來講:這兩個實例中使用的是編號捕獲分組和編號反向引用。每個分組會自動獲得一個編號,你可以使用這些編號來進行反向引用。

除了編號分組之外,現代的正則表達式流派還支持命名捕獲分組。在命名和編號分組之間的唯一區別是你可以給分組指派一個描述性的名稱,而不是被限製為隻能使用自動分配的編號。命名分組可以使正則表達式更加容易閱讀,更加容易維護。把一個捕獲分組添加到已有的正則表達式中可能會修改之前指派給所有捕獲分組的編號。但之前指派的名稱不會變化。

Python是第一個支持命名捕獲的正則表達式流派。它使用的語法是‹(?Pregex)›。名稱中包含的必須是可以被‹\w›匹配的單詞字符。‹(?P›是分組的起始括號,而‹)›則是結束括號。

.NET Regex類的設計人員為命名捕獲使用了自己的語法,他們使用了兩種可以互換的變體。‹(?regex)›模仿了Python的語法,但是卻去掉了其中的P。這裏的名稱必須由可以被‹\w›匹配的單詞字符組成。‹(?›是分組的起始括號,而‹)›則是結束括號。

如果你使用XML編碼,或是像我們這樣在DocBook XML中撰寫本書,在命名捕獲語法中的尖括號會給人帶來麻煩。所以.NET中提供了另一種替代命名捕獲語法:‹(?'name'regex)›。這裏的尖括號被替換為了單引號。你可以出於自己的方便來選擇使用哪種語法。它們的功能是完全等價的。

可能是基於.NET的流行超過了Python的原因,.NET語法似乎成為了其他正則庫開發人員更樂於接受的語法。Perl 5.10及更新版本使用的是它,而在Ruby 1.9的Oniguruma引擎中也是如此。Perl 5.10和Ruby 1.9同時支持使用尖括號和單引號的語法。Java 7同樣沿用了.NET的語法,但是僅支持尖括號這一方式。標準JavaScript不支持命名捕獲。XRegExp通過使用.NET的語法為其添加了命名捕獲支持,不過僅支持尖括號形式。

PCRE在很早的時候就沿用了的Python的語法,在那個時候Perl還根本沒有提供對於命名捕獲的支持。PCRE 7的版本中添加了Perl 5.10中添加的新功能,它同時提供對.NET語法和Python語法的支持。可能是基於PCRE成功的考慮,Perl 5.10采取了反向兼容的措施,也提供了對Python語法的支持。在PCRE和Perl 5.10中,用於命名捕獲的.NET語法和Python語法的功能是完全一樣的。

讀者應當選擇對你來說最為有用的語法。如果使用PHP編程,你會希望代碼能夠用於老版本的PHP,由於它采用的是PCRE舊版,你應該使用Python語法。如果不需要與舊版的兼容性,而且也同時使用.NET和Ruby的話,那麼在這些語言之間進行複製和粘貼時,采用.NET語法更為容易。如果你不是很確定,那麼可以使用PHP/PCRE中的Python語法。如果有人把你的代碼在舊版PCRE下進行了重新編譯,而代碼中的正則表達式突然就不工作了,那麼他們肯定會很不高興。當你把一個正則表達式從.NET複製到Ruby中時,刪掉一些P應該不是太困難。

PCRE 7和Perl 5.10的文檔中幾乎沒有提到Python語法,但是並不意味著它要被淘汰。正相反,我們實際上推薦在PCRE和PHP中使用Python語法。

命名反向引用
有了命名捕獲之後,緊接著就有了命名反向引用。正如命名捕獲分組與編號捕獲分組的功能完全相同一樣,命名反向引用與編號反向引用的功能也是完全相同的。它們隻是更加易於閱讀和維護。

Python使用語法‹(?P=name)›來創建對分組name的反向引用。雖然該語法使用的是圓括號,但反向引用並不是一個分組。你不能在名稱和結束括號之間放任何東西。一個反向引用‹(?P=name)›是一個獨立的正則表達式記號,就像是‹\1›一樣。PCRE和Perl 5.10也支持Python的命名反向引用語法。

.NET使用的語法是‹\k›和‹\k'name'›。這兩種形式在功能上是完全相同的,因此也可以隨意混合使用。使用尖括號語法創建的命名分組可以采用引號語法來進行引用,反之亦然。Perl 5.10、PCRE 7和Ruby 1.9也支持.NET的命名反向引用語法。Java 7和XRegExp則僅支持尖括號形式。

我們強烈推薦你不要在同一個正則表達式中混合使用命名和編號分組。不同流派對於出現在命名分組之間的非命名分組的編號方法會遵循不同的規則。Perl 5.10、Ruby1.9、Java 7和XRegExp沿用了.NET的語法,但是它們並沒有遵循.NET對於命名捕獲分組,或者是混合使用編號捕獲分組與命名分組進行編號的方式。與其在這裏解釋其中的差別,我們選擇推薦大家不要把命名和編號分組混合使用。應當避開可能的混淆,給各個未命名的分組賦予名稱,或者統一把它們變成非捕獲分組。

名稱相同的分組
Perl 5.10、Ruby 1.9和.NET允許多個命名捕獲分組共享相同的名稱。我們在實例4.5、8.7和8.19的解決方案中利用了這一特性。如果正則表達式使用選擇分支來查找特定文本的不同變體,使用相同名稱的捕獲分組可以使提取匹配的子串更加容易,而不用關心哪個分支匹配了文本。實例4.5中“單純正則表達式”一節使用選擇分支分別匹配不同長度的月份日期。每個分支匹配一組日期和月份。因為在不同的分支中使用相同的“day”和“month”分組名稱,我們隻需要在正則表達式找到匹配後查詢兩個捕獲分組即可獲取日期和月份。

本書中其他支持命名捕獲的正則流派將多個名稱相同的分組視為錯誤。

screenshot多個捕獲分組使用相同名稱僅在隻有一個分組參與匹配時可靠工作。本書中所有使用多個相同名稱捕獲分組的實例都是這種情況。各個分組處於不同選擇分支中,而且選擇分支不處於重複的分組中。Perl 5.10、Ruby 1.9和.NET的確允許兩個相同名稱的分組同時參與匹配。但反向引用的行為和匹配之後分組保存的文本在這些流派中各有不同。對我們來說,推薦在不同的選擇分支中相同名稱的分組就已經足夠複雜了。

最後更新:2017-06-06 07:35:32

  上一篇:go  《正則表達式經典實例(第2版)》——2.12 把正則表達式的一部分重複多次
  下一篇:go  為什麼要選擇CS專業,助力未來IT人的報考指南