正則表示式最短匹配
正則表達
貪婪與懶惰
當正則表示式中包含能接受重複的限定符時,通常的行為是(在使整個表示式能得到匹配的前提下)匹配儘可能多的字元。考慮這個表示式:a.*b,它將會匹配最長的以a開始,以b結束的字串。如果用它來搜尋aabab的話,它會匹配整個字串aabab。這被稱為貪婪匹配。
有時,我們更需要懶惰匹配,也就是匹配儘可能少的字元。前面給出的限定符都可以被轉化為懶惰匹配模式,只要在它後面加上一個問號?。這樣.*?就意味著匹配任意數量的重複,但是在能使整個匹配成功的前提下使用最少的重複。現在看看懶惰版的例子吧:
a.*?b匹配最短的,以a開始,以b結束的字串。如果把它應用於aabab的話,它會匹配aab(第一到第三個字元)
為什麼第一個匹配是aab(第一到第三個字元)而不是ab(第二到第三個字元)?簡單地說,因為正則表示式有另一條規則,比懶惰/貪婪規則的優先順序更高:最先開始的匹配擁有最高的優先權——The match that begins earliest wins。
程式碼/語法 | 說明 |
---|---|
*? | 重複任意次,但儘可能少重複 |
+? | 重複1次或更多次,但儘可能少重複 |
?? | 重複0次或1次,但儘可能少重複 |
{n,m}? | 重複n到m次,但儘可能少重複 |
{n,}? | 重複n次以上,但儘可能少重複 |
處理選項
在C#中,你可以使用Regex(String, RegexOptions)建構函式
上面介紹了幾個選項如忽略大小寫,處理多行等,這些選項能用來改變處理正則表示式的方式。下面是.Net中常用的正則表示式選項:
名稱 | 說明 |
---|---|
IgnoreCase(忽略大小寫) | 匹配時不區分大小寫。 |
Multiline(多行模式) | 更改^和$的含義,使它們分別在任意一行的行首和行尾匹配,而不僅僅在整個字串的開頭和結尾匹配。(在此模式下,$的精確含意是:匹配/n之前的位置以及字串結束前的位置.) |
Singleline(單行模式) |
更改.的含義,使它與每一個字元匹配(包括換行符/n)。 |
IgnorePatternWhitespace(忽略空白) | 忽略表示式中的非轉義空白並啟用由#標記的註釋。 |
RightToLeft(從右向左查詢) | 匹配從右向左而不是從左向右進行。 |
ExplicitCapture(顯式捕獲) | 僅捕獲已被顯式命名的組。 |
ECMAScript(JavaScript相容模式) | 使表示式的行為與它在JavaScript裡的行為一致。 |
一個經常被問到的問題是:是不是隻能同時使用多行模式和單行模式中的一種?答案是:不是。這兩個選項之間沒有任何關係,除了它們的名字比較相似(以至於讓人感到疑惑)以外。
平衡組/遞迴匹配
這裡介紹的平衡組語法是由.Net Framework支援的;其它語言/庫不一定支援這種功能,或者支援此功能但需要使用不同的語法。
有時我們需要匹配像( 100 * ( 50 + 15 ) )這樣的可巢狀的層次性結構,這時簡單地使用/(.+/)則只會匹配到最左邊的左括號和最右邊的右括號之間的內容(這裡我們討論的是貪婪模式,懶惰模式也有下面的問題)。假如原來的字串裡的左括號和右括號出現的次數不相等,比如( 5 / ( 3 + 2 ) ) ),那我們的匹配結果裡兩者的個數也不會相等。有沒有辦法在這樣的字串裡匹配到最長的,配對的括號之間的內容呢?
為了避免(和/(把你的大腦徹底搞糊塗,我們還是用尖括號代替圓括號吧。現在我們的問題變成了如何把xx <aa <bbb> <bbb> aa> yy這樣的字串裡,最長的配對的尖括號內的內容捕獲出來?
這裡需要用到以下的語法構造:
- (?'group') 把捕獲的內容命名為group,並壓入堆疊(Stack)
- (?'-group') 從堆疊上彈出最後壓入堆疊的名為group的捕獲內容,如果堆疊本來為空,則本分組的匹配失敗
- (?(group)yes|no) 如果堆疊上存在以名為group的捕獲內容的話,繼續匹配yes部分的表示式,否則繼續匹配no部分
- (?!) 零寬負向先行斷言,由於沒有後綴表示式,試圖匹配總是失敗
如果你不是一個程式設計師(或者你自稱程式設計師但是不知道堆疊是什麼東西),你就這樣理解上面的三種語法吧:第一個就是在黑板上寫一個"group",第二個就是從黑板上擦掉一個"group",第三個就是看黑板上寫的還有沒有"group",如果有就繼續匹配yes部分,否則就匹配no部分。
我們需要做的是每碰到了左括號,就在壓入一個"Open",每碰到一個右括號,就彈出一個,到了最後就看看堆疊是否為空--如果不為空那就證明左括號比右括號多,那匹配就應該失敗。正則表示式引擎會進行回溯(放棄最前面或最後面的一些字元),儘量使整個表示式得到匹配。
< #最外層的左括號
[^<>]* #最外層的左括號後面的不是括號的內容
(
(
(?'Open'<) #碰到了左括號,在黑板上寫一個"Open"
[^<>]* #匹配左括號後面的不是括號的內容
)+
(
(?'-Open'>) #碰到了右括號,擦掉一個"Open"
[^<>]* #匹配右括號後面不是括號的內容
)+
)*
(?(Open)(?!)) #在遇到最外層的右括號前面,判斷黑板上還有沒有沒擦掉的"Open";如果還有,則匹配失敗
> #最外層的右括號
平衡組的一個最常見的應用就是匹配HTML,下面這個例子可以匹配巢狀的<div>標籤:<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.
還有些什麼東西沒提到
我已經描述了構造正則表示式的大量元素,還有一些我沒有提到的東西。下面是未提到的元素的列表,包含語法和簡單的說明。你可以在網上找到更詳細的參考資料來學習它們--當你需要用到它們的時候。如果你安裝了MSDN Library,你也可以在裡面找到關於.net下正則表示式詳細的文件。
程式碼/語法 | 說明 |
---|---|
/a | 報警字元(列印它的效果是電腦嘀一聲) |
/b | 通常是單詞分界位置,但如果在字元類裡使用代表退格 |
/t | 製表符,Tab |
/r | 回車 |
/v | 豎向製表符 |
/f | 換頁符 |
/n | 換行符 |
/e | Escape |
/0nn | ASCII程式碼中八進位制程式碼為nn的字元 |
/xnn | ASCII程式碼中十六進位制程式碼為nn的字元 |
/unnnn | Unicode程式碼中十六進位制程式碼為nnnn的字元 |
/cN | ASCII控制字元。比如/cC代表Ctrl+C |
/A | 字串開頭(類似^,但不受處理多行選項的影響) |
/Z | 字串結尾或行尾(不受處理多行選項的影響) |
/z | 字串結尾(類似$,但不受處理多行選項的影響) |
/G | 當前搜尋的開頭 |
/p{name} | Unicode中命名為name的字元類,例如/p{IsGreek} |
(?>exp) | 貪婪子表示式 |
(?<x>-<y>exp) | 平衡組 |
(?im-nsx:exp) | 在子表示式exp中改變處理選項 |
(?im-nsx) | 為表示式後面的部分改變處理選項 |
(?(exp)yes|no) | 把exp當作零寬正向先行斷言,如果在這個位置能匹配,使用yes作為此組的表示式;否則使用no |
(?(exp)yes) | 同上,只是使用空表示式作為no |
(?(name)yes|no) | 如果命名為name的組捕獲到了內容,使用yes作為表示式;否則使用no |
(?(name)yes) | 同上,只是使用空表示式作為no |