正则表达式高级用法(分组与捕获)

分组的引入:

    对于要重复单个字符,非常简单,直接在字符后卖弄加上限定符即可,例如 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]ad*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+.dd[1-9]?d+

在这种条件下 6.625 能进行匹配,这样做没有必要,因为它本身就是三位小数。最后一个“5”本来是给 [1-9] 匹配的,但是后面还有一个 d+ 所以,[1-9] 由于是“?”可以不匹配所以只能放弃当前的匹配,将这个“5”送给 d+ 去匹配,如果改为:

d+.dd[1-9]?+d+

的侵占形式,在“5”匹配到 [1-9] 时,由于是侵占式的,所以不会进行回溯,后面的 d+ 就匹配不到任东西了,所以导致 6.625 匹配失败。

这种情况,在替换时就有效了,比如把数字截短到小数点后三位,如果正好是三位小数的,就可以不用替换了,可以提高效率,侵占量词基本上就是用来提高匹配效率的。

把 d+.dd[1-9]?+d+ 改为 d+.dd(?>[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 = @"(?<protocol>S+)://(?<address>S+)";
Response.Write(pattern.Replace("<", "&lt;").Replace(">", "&gt;") + "<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.
    (?<protocol>S+)://(?<address>S+)

    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

原文地址:https://www.cnblogs.com/zhaoshujie/p/10103315.html