《正則表達式經典實例(第2版)》——2.9 分組和捕獲匹配中的子串
本節書摘來自異步社區《正則表達式經典實例(第2版)》一書中的第2章,第2.9節,作者: 【美】Jan Goyvaerts , Steven Levithan著,更多章節內容可以訪問雲棲社區“異步社區”公眾號查看
2.9 分組和捕獲匹配中的子串
問題描述
改進匹配Mary、Jane或Sue的正則表達式,使之隻能匹配完整單詞。使用分組來實現這個功能,整個正則表達式隻需要一對單詞分界符,而不是給每個選擇分支都使用一對分界符。
創建一個正則表達式,使之匹配yyyy-mm-dd格式的任意日期,並且分別捕獲年、月和日。目標是在處理匹配的代碼中可以更容易處理這些分別捕獲的值。你可以假設目標文本中的所有日期都是合法的。正則表達式不必要考慮去掉像9999-99-99這樣的非法數據,因為它們根本不可能出現在目標文本中。
解決方案
\b(Mary|Jane|Sue)\b
正則選項:無
正則流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby
\b(\d\d\d\d)-(\d\d)-(\d\d)\b
正則選項:無
正則流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby
討論
在上一節中介紹過的選擇分支操作符(alternation operator)在所有正則操作符中擁有最低的優先級。如果你嚐試的是‹\bMary|Jane|Sue\b›,那麼三個選擇分支分別是‹\bMary›、‹Jane›和‹Sue\b›。這個正則式會匹配Her name is Janet中的Jane。
如果想要正則表達式中的一些內容不受選擇分支影響的話,那麼你就需要把這些選擇分支進行分組。分組是通過圓括號來實現的。它們擁有在所有正則操作符中的最高優先級,這與絕大多數編程語言都是一致的。‹\b(Mary|Jane|Sue)\b›擁有三個選擇分支‹Mary›、‹Jane›和‹Sue›—它們都位於兩個單詞分界符之間。這個正則式在Her name is Janet中不會匹配任何單詞。
當正則引擎到達目標文本中的Janet的J位置時,會匹配第一個單詞分界符,接著該引擎就會進入這個分組。分組中的第一個選擇分支‹Mary›匹配失敗。第二個選擇分支‹Jane›匹配成功。然後引擎就會退出該分組。隻剩下了‹\b›。然而單詞分界符無法匹配這裏的e與目標結尾處的t之間的位置。在J位置開始的匹配其最後的結果是失敗。
一組圓括號不僅僅是一個分組;它還是一個捕獲分組(capturing group)。對於前麵的Mary-Jane-Sue的正則表達式來說,捕獲並不是很有用,因為它隻是簡單地匹配這個正則表達式。當需要包含部分正則表達式的時候,捕獲才會有用,就像在正則式‹\b(\d\d\d\d)-(\d\d)-(\d\d)\b›中。
這個正則表達式匹配一個yyyy-mm-dd格式的日期。正則表達式‹\b\d\d\d\d-\d\d-\d\d\b›也會匹配完全一樣的內容。因為這個正則式沒有使用任何選擇分支或者重複,因此圓括號的分組功能就沒必要存在。不過捕獲功能用起來是很方便的。
正則表達式‹\b(\d\d\d\d)-(\d\d)-(\d\d)\b›擁有三個捕獲分組。分組是按照左括號的順序從左向右進行編號的。‹(\d\d\d\d)›是1號分組,‹(\d\d)›是2號,第二個‹(\d\d)›是3號分組。
在匹配過程中,當正則表達式引擎到達右括號而退出分組的時候,它會把該捕獲分組所匹配的文本的子串存儲起來。當我們的正則式匹配2008-05-24的時候,2008被存儲到第1個捕獲分組中,05在第2個捕獲分組,而24則在第3個捕獲分組中。
有3種使用捕獲文本的方式。實例2.10會講解如何在同一個正則匹配中再次匹配所捕獲的文本。實例2.21會展示在執行查找和替換的時候,如何把捕獲到的文本添加到替代文本中。實例3.9會介紹在你的應用程序中如何使用正則匹配的子串。
變體
非捕獲分組
在正則式‹\b(Mary|Jane|Sue)\b›中,我們使用圓括號隻是為了分組的目的。與其使用一個捕獲分組,我們也可以使用非捕獲分組。
\b(?:Mary|Jane|Sue)\b
正則選項:無
正則流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby
由三個字符‹(?:›作為起始的是一個非捕獲分組。右括號‹)›則作為該分組的結束。非捕獲分組提供相同的分組功能,但是不會捕獲任何內容。
在計算捕獲分組的左括號來確定其序號的時候,不會計算非捕獲分組的括號。這也是非捕獲分組的主要好處:你可以把它們添加到已有的正則表達式中,而不會破壞對於已經編號的捕獲分組的引用。
非捕獲分組的另外一個好處是它的性能較高。如果你不會使用到某個特定分組的反向引用(實例2.10),需要把它重新插入到替代文本中(實例2.21),或是在源代碼中提取其匹配(實例3.9)的話,那麼使用捕獲分組就會添加額外的性能開銷,這可以通過使用非捕獲分組來消除。在實踐中,你可能很少會注意到性能上的差異,除非在一個代碼很少但循環次數很多的“緊閉循環”(tight loop)中使用正則表達式,並且/或者使用它來處理大量數據。
帶模式修飾符的分組
在實例2.1的“不區分大小寫的匹配”變體中,我們解釋了.NET、Java、PCRE、Perl和Ruby支持局部模式修飾符,其中使用了模式切換:‹sensitive(?i)caseless (?-i)sensitive›。雖然這種語法也會涉及到圓括號,但像‹(?i)›這樣的切換並不會影響到任何分組。
如果不用切換,你也可以在非捕獲分組中來指定模式修飾符。
\b(?i:Mary|Jane|Sue)\b
正則選項:無
正則流派:.NET、Java、PCRE、Perl、Ruby
sensitive(?i:caseless)sensitive
正則選項:無
正則流派:.NET、Java、PCRE、Perl、Ruby
在非捕獲分組中添加模式修飾符會對在該分組中的正則表達式子串設置該模式。而在右括號之後則會恢複之前的設置。因為默認來說正則表達式是區分大小寫的,因此隻有在 (?i:⋯) 之內的正則表達式部分才是不區分大小寫的。
最後更新:2017-06-02 19:35:39