分組的引入:
對於要重復單個字符,非常簡單,直接在字符后賣弄加上限定符即可,例如 a+ 表示匹配1個或一個以上的a,a?表示匹配0個或1個a。這些限定符如下所示:
X ? |
X ,一次或一次也沒有 |
X * |
X ,零次或多次 |
X + |
X ,一次或多次 |
X { n } |
X ,恰好 n 次 |
X { n ,} |
X ,至少 n 次 |
X { n , m } |
X ,至少 n 次,但是不超過 m 次 |
但是我們如果要對多個字符進行重復怎么辦呢?此時我們就要用到分組,我們可以使用小括號"()"來指定要重復的子表達式,然后對這個子表達式進行重復,例如:(abc)? 表示0個或1個abc 這里一 個括號的表達式就表示一個分組 。
分組可以分為兩種形式,捕獲組和非捕獲組。
一、捕獲組
捕獲組可以通過從左到右計算其開括號來編號 。例如,在表達式 (A)(B(C)) 中,存在四個這樣的組:
0 |
(A)(B(C)) |
1 |
(A) |
2 |
(B(C)) |
3 |
(C) |
組零始終代表整個表達式
之所以這樣命名捕獲組是因為在匹配中,保存了與這些組匹配的輸入序列的每個子序列。捕獲的子序列稍后可以通過 Back 引用(反向引用) 在表達式中使用,也可以在匹配操作完成后從匹配器檢索。
Back 引用 是說在后面的表達式中我們可以使用組的編號來引用前面的表達式所捕獲到的文本序列。注意:反向引用,引用的是前面捕獲組中的文本而不是正則,也就是說反向引用處匹配的文本應和前面捕獲組中的文本相同,這一點很重要。
【例】(["']).*\1
其中使用了分組,\1就是對引號這個分組的引用,它匹配包含在兩個引號或者兩個單引號中的所有字符串,如,"abc" 或 " ' " 或 ' " ' ,但是請注意,它並不會對" a'或者 'a"匹配。原因上面已經說明,Back引用只是引用文本而不是表達式。
分組舉列
先來看第一個作用,對於IP地址的匹配,簡單的可以寫為如下形式:
\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}
但仔細觀察,我們可以發現一定的規律,可以把.\d{1,3}看成一個整體,也就是把他們看成一組,再把這個組重復3次即可。表達式如下:
\d{1,3}(.\d{1,3}){3}
再來看第二個作用,就拿匹配<title>xxx</title>標簽來說,簡單的正則可以這樣寫:
<title>.*</title>
可以看出,上邊表達式中有兩個title,完全一樣,其實可以通過分組簡寫。表達式如下:
<(title)>.*</\1>
對於分組而言,整個表達式永遠算作第0組,在本例中,第0組是<(title)>.*</\1>,然后從左到右,依次為分組編號,因此,(title)是第1組。
注意:
用\1這種語法,可以引用某組的文本內容,但不能引用正則表達式。
例如剛剛的IP地址正則表達式為\d{1,3}(.\d{1,3}){3},里邊的\d{1,3}重復了兩次,如果利用后向引用簡化,表達式如下:
(\d{1,3})(.\1){3}
經過實際測試,會發現這樣寫是錯誤的,為什么呢?
后向引用,引用的僅僅是文本內容,而不是正則表達式!
也就是說,組中的內容一旦匹配成功,后向引用,引用的就是匹配成功后的內容,引用的是結果,而不是表達式。
因此,(\d{1,3})(.\1){3}這個表達式實際上匹配的是四個數都相同的IP地址,比如:123.123.123.123。
二、非捕獲組
以 (?) 開頭的組是純的非捕獲 組,它不捕獲文本 ,也不針對組合計進行計數。就是說,如果小括號中以?號開頭,那么這個分組就不會捕獲文本,當然也不會有組的編號,因此也不存在Back 引用。
我們通過捕獲組就能夠得到我們想要匹配的內容了,那為什么還要有非捕獲組呢?原因是捕獲組捕獲的內容是被存儲在內存中,可供以后使用,比如反向引用就是引用的內存中存儲的捕獲組中捕獲的內容。而非捕獲組則不會捕獲文本,也不會將它匹配到的內容單獨分組來放到內存中。所以,使用非捕獲組較使用捕獲組更節省內存。在實際情況中我們要酌情選用。
1、非捕獲組(?:Pattern)
它的作用就是匹配Pattern字符,好處就是不捕獲文本,不將匹配到的字符存儲到內存中,從而節省內存。
【例】匹配indestry或者indestries
我們可以使用indestr(y|ies)或者indestr(?:y|ies)
【例】(?:a|A)123(?:b)可以匹配a123b或者A123b
非捕獲組有很多種形式,其中包括:零寬度斷言和模式修正符
2、零寬度斷言
(?= X ) |
X ,通過零寬度的正 lookahead |
字符串:product_path 正則:(product)(?=_path) 結果:product |
(?! X ) |
X ,通過零寬度的負 lookahead |
字符串:product_path 正則:(product)(?!_url) 結果:product 字符串:product_path 正則:(product)(?!_path) 結果:空 |
(?<= X ) |
X ,通過零寬度的正 lookbehind |
字符串:name:wangfei 正則:(?<=name:)(wangfei) 結果:wangfei |
(?<! X ) |
X ,通過零寬度的負 lookbehind |
字符串:name:angelica 正則:(?<!nick_name:)(angelica) 結果:angelica 字符串:name:angelica 正則:(?<!name:)(angelica) 結果:空 |
這四個非捕獲組用於匹配表達式X,但是不包含表達式的文本。
(?=X ) |
零寬度正先行斷言。僅當子表達式 X 在此位置的右側匹配時才繼續匹配。也就是說要使此零寬度斷言起到我們想要的效果的話,就必須把這個非捕獲組放在整個表達式的右側。例如,/w+(?=/d) 與后跟數字的單詞匹配,而不與該數字匹配。此構造不會回溯。 |
(?!X) |
零寬度負先行斷言。僅當子表達式 X 不在此位置的右側匹配時才繼續匹配。例如,例如,/w+(?!/d) 與后不跟數字的單詞匹配,而不與該數字匹配 。 |
(?<=X) |
零寬度正后發斷言。僅當子表達式 X 在此位置的左側匹配時才繼續匹配。例如,(?<=19)99 與跟在 19 后面的 99 的實例匹配。此構造不會回溯。 |
(?<!X) |
零寬度負后發斷言。僅當子表達式 X 不在此位置的左側匹配時才繼續匹配。例如,(?<!19)99 與不跟在 19 后面的 99 的實例匹配 |
上面都是理論性的介紹,這里就使用一些例子來說明一下問題:
【例1】正則表達式 (?<!4)56(?=9)
含義:查找56,要求前面不能是4,后面必須是9。因此,可以匹配如下文本 5569 ,與4569不匹配。
【例2】提取字符串 da12bka3434bdca4343bdca234bm中包含在字符a和b之間的數字,但是這個a之前的字符不能是c;b后面的字符必須是d才能提取。
顯然,這里就只有3434這個數字滿足要求。那么我們怎么提取呢?
首先,我們寫出含有捕獲組的正則表達式:[^c]a\d*bd 然后我們再將其變為非捕獲組的正則表達式:(?<=[^c]a)\d*(?=bd)
3、模式修正符
以(?)開頭的非捕獲組除了零寬度斷言之外,還有模式修正符。
正則表達式中常用的模式修正符有i、g、m、s、x、e等。它們之間可以組合搭配使用。
(?imnsx-imnsx: ) 應用或禁用子表達式中指定的選項。例如,(?i-s: ) 將打開不區分大小寫並禁用單行模式。關閉不區分大小寫的開關可以使用(?-i)。有關更多信息,請參閱正則表達式選項。
【例1】(?i)ab
表示對(?i)后的所有字符都開啟不區分大小寫的開關。故它可以匹配ab、aB、Ab、AB
【例2】(?i:a)b
它表示只對a開啟不區分大小寫的開關。故它可以匹配ab和Ab。不能匹配aB和AB。
4、(?>Pattern)等同於侵占模式
匹配成功不進行回溯,這個比較復雜,與侵占量詞“+”可以通用,比如:\d++ 可以寫為 (?>\d+)。
【例】將一些多位的小數截短到三位小數:\d+\.\d\d[1-9]?\d+
在這種條件下 6.625 能進行匹配,這樣做沒有必要,因為它本身就是三位小數。最后一個“5”本來是給 [1-9] 匹配的,但是后面還有一個 \d+ 所以,[1-9] 由於是“?”可以不匹配所以只能放棄當前的匹配,將這個“5”送給 \d+ 去匹配,如果改為:
\d+\.\d\d[1-9]?+\d+
的侵占形式,在“5”匹配到 [1-9] 時,由於是侵占式的,所以不會進行回溯,后面的 \d+ 就匹配不到任東西了,所以導致 6.625 匹配失敗。
這種情況,在替換時就有效了,比如把數字截短到小數點后三位,如果正好是三位小數的,就可以不用替換了,可以提高效率,侵占量詞基本上就是用來提高匹配效率的。
把 \d+\.\d\d[1-9]?+\d+ 改為 \d+\.\d\d(?>[1-9]?)\d+ 這樣是一樣的。
【補充】js獲取分組內容的方法:(可參考JS正則實例)
1、 arr[n] = str.match(reg); 或者 arr[n] = reg.exec(str);
返回的匹配數組arr[n]中,arr[0]表示整個匹配,arr[1],arr[2].......分別表示各個分組的匹配結果
2、通過RegExp對象的靜態屬性來獲取
RegExp.$1,RegExp.$2.........RegExp.$9 分別表示匹配到的第一個分組至第九個分組的內容
例:
var str = "adfasd324232sdfas"; alert(str); var reg = new RegExp("([a-z]*)(\\d*)([a-z]*)"); var arr = str.match(reg); alert(arr[0] + "===" + arr[1] + "===" + arr[2] + "===" + arr[3]); alert(RegExp.$1 + "-----" + RegExp.$2 + "----" + RegExp.$3);
Group 分組
在一個正則表達式中,如果要提取出多個不同的部分(子表達式項),需要用到分組功能。
在 C# 正則表達式中,Regex 成員關系如下,其中 Group 是其分組處理類。
Regex –> MatcheCollection (匹配項集合)
–> Match (單匹配項 內容)
–> GroupCollection (單匹配項中包含的 "(分組/子表達式項)" 集合)
–> Group ( "(分組/子表達式項)" 內容)
–> CaputerCollection (分組項內容顯示基礎?)
–> Caputer
Group 對分組有兩種訪問方式:
1、數組下標訪問
在 ((\d+)([a-z]))\s+ 這個正則表達式里總共包含了四個分組,按照默認的從左到右的匹配方式,
Groups[0] 代表了匹配項本身,也就是整個整個表達式 ((\d+)([a-z]))\s+
Groups[1] 代表了子表達式項 ((\d+)([a-z]))
Groups[2] 代表了子表達式項 (\d+)
Groups[3] 代表了子表達式項 ([a-z])
string text = "1A 2B 3C 4D 5E 6F 7G 8H 9I 10J 11Q 12J 13K 14L 15M 16N ffee80 #800080"; Response.Write(text + "<br/>"); string strPatten = @"((\d+)([a-z]))\s+"; Regex rex = new Regex(strPatten, RegexOptions.IgnoreCase); MatchCollection matches = rex.Matches(text); //提取匹配項 foreach (Match match in matches) { GroupCollection groups = match.Groups; Response.Write(string.Format("<br/>{0} 共有 {1} 個分組:{2}<br/>" , match.Value, groups.Count, strPatten)); //提取匹配項內的分組信息 for (int i = 0; i < groups.Count; i++) { Response.Write( string.Format("分組 {0} 為 {1},位置為 {2},長度為 {3}<br/>" , i , groups[i].Value , groups[i].Index , groups[i].Length)); } } /* * 輸出: 1A 2B 3C 4D 5E 6F 7G 8H 9I 10J 11Q 12J 13K 14L 15M 16N ffee80 #800080 1A 共有 4 個分組:((\d+)([a-z]))\s+ 分組 0 為 1A ,位置為 0,長度為 3 分組 1 為 1A,位置為 0,長度為 2 分組 2 為 1,位置為 0,長度為 1 分組 3 為 A,位置為 1,長度為 1 .... */
2、命名訪問
利用 (?<xxx>子表達式) 定義分組別名,這樣就可以利用 Groups["xxx"] 進行訪問分組/子表達式內容。
string text = "I've found this amazing URL at http://www.sohu.com, and then find ftp://ftp.sohu.comisbetter."; Response.Write(text + "<br/>"); string pattern = @"\b(?<protocol>\S+)://(?<address>\S+)\b"; Response.Write(pattern.Replace("<", "<").Replace(">", ">") + "<br/><br/>"); MatchCollection matches = Regex.Matches(text, pattern); foreach (Match match in matches) { GroupCollection groups = match.Groups; Response.Write(string.Format( "URL: {0}; Protocol: {1}; Address: {2} <br/>" , match.Value , groups["protocol"].Value , groups["address"].Value)); } /* * 輸出 I've found this amazing URL at http://www.sohu.com, and then find ftp://ftp.sohu.comisbetter. \b(?<protocol>\S+)://(?<address>\S+)\b URL: http://www.sohu.com; Protocol: http; Address: www.sohu.com URL: ftp://ftp.sohu.comisbetter; Protocol: ftp; Address: ftp.sohu.comisbetter */
內容參考自:
C#正則表達式編程(三):Match類和Group類用法 http://blog.csdn.net/zhoufoxcn/archive/2010/03/09/5358644.aspx
C#正則表達式類Match和Group類的理解 http://tech.ddvip.com/2008-10/122483707982616.html