5.4. C#正则表达式的使用心得或注意事项

5.4.1. 正则表达式字符串的写法

正则表达式是用字符串前面加上@字符来表示的。

5.4.2. 正则表达式中包含双引号,是用两个双引号去表示

其中如果包含的字符串中包含双引号,那么就两个双引号表示,而不是反斜杠加上双引号(\”),也不是斜杠加上双引号(/”)

示例代码:

string pat = @"var\s+primedResponse=\{""items""\:\[\{(.*)}\]\};\s+\$Do\.register\(""primedResponse""\);";
Regex rx = new Regex(pat,RegexOptions.Compiled);
        

5.4.3. C#中的named group和后向引用

C#中,对于前面所匹配的字符组名的替换和(向后)引用,是不一样的

替换是这样的写法:${name}

而向后引用是这样的写法:\k<name>

5.4.4. 关于字符点,匹配任意字符,但是不匹配换行(\n)的事情

一般对于介绍正则表达式的时候,为了简便,都说成字符点(.),是匹配任何单个字符的。

有的解释的严谨的,也会加上一句,在非单行匹配模式下,匹配除了换行\n之外的任意字符。

此句话有更深层的含义:

5.4.4.1. 关于默认情况下就是非单行模式,所以默认情况,点是不匹配换行字符\n的

一般来说,调用的正则表达式的库函数,比如C#中的用new Regex生成对应的Regex变量的时候:

Regex imgRx = new Regex(imgP); 
MatchCollection foundImg = imgRx.Matches(curSelectCotent);
            

默认就是多行模式,所以如果像我这里,如果去匹配的内容,涉及到中间包括\n字符,那么默认情况下,是无法匹配的,

导致程序调试花了半天时间,才找到上述原因。

例 5.1. 默认的点'.'不匹配换行\n

对于一堆字符:


<P><A 
href="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles6977DF/F200908260947322978810268[2].jpg"><IMG 
style="BACKGROUND-IMAGE: none; BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px" 
title=F200908260947322978810268 border=0 alt=F200908260947322978810268 
src="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles6977DF/F200908260947322978810268_thumb.jpg" 
width=206 height=244></A>&nbsp;</P> 
<P><A 
href="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles6977DF/王珞丹 图片22[3].jpg"><IMG 
style="BACKGROUND-IMAGE: none; BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px" 
title="王珞丹 图片22" border=0 alt="王珞丹 图片22" 
src="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles6977DF/王珞丹 图片22_thumb.jpg" 
width=176 height=244></A><A 
href="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles6977DF/王珞丹 图片333[2].jpg"><IMG 
style="BACKGROUND-IMAGE: none; BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px" 
title="王珞丹 图片333" border=0 alt="王珞丹 图片333" 
src="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles6977DF/王珞丹 图片333_thumb.jpg" 
width=230 height=244></A></P>

                

然后用下面的代码去匹配:

string imgP = @"href=""(file:.+?WindowsLiveWriter.+?)"".+?src=""(file:.+?WindowsLiveWriter.+?)"""; 
Regex imgRx = new Regex(imgP); 
MatchCollection foundImg = imgRx.Matches(curSelectCotent); 
                

则其中的"".+?src=""中的.+?是无法匹配成功的,因为上述输入的字符串中,包括了换行符,导致此处无法匹配。

而只有把

Regex imgRx = new Regex(imgP);

改为:

Regex imgRx = new Regex(imgP, RegexOptions.Singleline);

才是可以的。

即设置为单行模式,此处点(.)就匹配,包括\n在内的任意单个字符了。

其他语言中,比如Python等,也有对应的single line,multi line等方面的设置,可根据自行需要设置。


5.4.4.2. 匹配换行\n之外的任意字符,那么也包括了回车\r字符

所以,万一你的应用中,需要涉及到这个字符,(如果不是单行模式的话),要注意的是点(.)是匹配\r,但不匹配\n的。

5.4.5. C#中如何获得带名字name的捕获capture的组group

对于输入字符串为:


<A 
href="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22[3].jpg"><IMG 
style="BACKGROUND-IMAGE: none; BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px" 
title="王珞丹 图片22" border=0 alt="王珞丹 图片22" 
src="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22_thumb.jpg" 
width=176 height=244></A>

        

用下面的代码:


string imgP = @"href=""(file:///(?<localAddrPref>.+?WindowsLiveWriter.+?/supfile[^/]+?)/(?<realName>[^""]+?)\[\d+\](\.(?<suffix>\w{3,4}))?)"".+?src=""(file:///\k<localAddrPref>/\k<realName>_thumb(\.\k<suffix>)?)""";
Regex imgRx = new Regex(imgP, RegexOptions.Singleline);
MatchCollection foundImg = imgRx.Matches(curSelectCotent);

        

是可以获得所匹配的group的,但是想要对应带名字的的那些变量,比如localAddrPref,suffix等值,却不知道如何获得。

其实,根据Grouping Constructs的解释,也已经知道如何可以获得,只是觉得那种方法不够好,希望可以找到对应的capture对应的自己的处理方法。

后来找了下,参考Regex: Named Capturing Groups in .NET才明白,原来对于带名字的匹配的group,其实也是group,但是是对于每一个match来说,都有对应的自己的capture的。

结果去试了试,发现如果写成这样:


CaptureCollection captures = found.Captures;
localAddrPref   = captures[0].Value;
title           = captures[1].Value; // will lead to code run dead !

        

captures[0]是整个的字符串,而不是对应的第一个name group的值,而captures[1],则直接导致程序死掉,即不能这样调用。

另外,在Regex: get the name of captured groups in C#也找到了用GetGroupNames的方式,有空也去试试。

看到Regular Expression Classes解释的,可以直接用groupname取得对应的值的,即之前都是用found.Groups[3].ToString()而换作found.Groups["localAddrPref"].Value即可。

其中localAddrPref是前面的某个group的name。

5.4.6. C#中,对于Regex中带嵌套的括号,那么group是的index如何计算,以及Regex中包含了带name的group,group又是如何计算的。

此处的逻辑,是根据官方的解释加上实际调试所得到的结果,来解释说明的。

举例,对于输入字符串:


<A
href="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22[3].jpg"><IMG 
style="BACKGROUND-IMAGE: none; BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; DISPLAY: inline; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px" 
title="王珞丹 图片22" border=0 alt="王珞丹 图片22" 
src="file:///C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22_thumb.jpg" 
width=176 height=244></A>

        

用这样的代码去匹配:


string imgP = @"href=""file:///((?<localAddrPref>.+?WindowsLiveWriter.+?/supfile[^/]+?)/(?<realName>[^""]+?)\[\d+\](\.(?<suffix>\w{3,4}))?)"".+?title=""?(\k<realName>)?""?.+?alt=""?(\k<realName>)?""?.+?src=""file:///(\k<localAddrPref>/\k<realName>_thumb(\.\k<suffix>)?)""";           
Regex imgRx = new Regex(imgP, RegexOptions.Singleline);
MatchCollection foundImg = imgRx.Matches(curSelectCotent);

        

则匹配的结果是:

_groups {System.Text.RegularExpressions.Group[9]} System.Text.RegularExpressions.Group[]
[0] {C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22[3].jpg} System.Text.RegularExpressions.Group
[1] {.jpg} System.Text.RegularExpressions.Group
[2] {王珞丹 图片22} System.Text.RegularExpressions.Group 
[3] {王珞丹 图片22} System.Text.RegularExpressions.Group
[4] {C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22_thumb.jpg} System.Text.RegularExpressions.Group
[5] {.jpg} System.Text.RegularExpressions.Group
[6] {C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15} System.Text.RegularExpressions.Group
[7] {王珞丹 图片22} System.Text.RegularExpressions.Group
[8] {jpg} System.Text.RegularExpressions.Group
        

注意,上述内容是debug所查看到的。

group[0]不是整个字符串,而是表示此处匹配的第一个结果,即index从0开始,而不是默认的1.

不过下面还是以代码中的写法来解释。

5.4.6.1. C#中,如果Regex中包括了子括号,即括号嵌套括号的时候,group的index是如何计算的,以及如果Regex中包括了带name的group,group的index是如何计算的

搞了半天,最后终于看懂了。

现在来说说,如何数group的index。

其实很简单:

  1. 首先,所有的group,分两类,一类是默认的,无名字(unnamed的group),另外一类是有名字的group(name的group)。
  2. 然后对于group的index,从左至右,以左括号出现的位置先后为标准,一个个的数一下对应的unnamed的group,等全部数完了,再去安装同样标准去数有名字的group。
  3. 或者可以完全不去数有name的group,因为有name的group的值,完全可以直接通过Groups["yorGroupName"].Value的形式得到。

举例说明:

对于例子中的匹配样式:


href=""file:///((?<localAddrPref>.+?WindowsLiveWriter.+?/supfile[^/]+?)/(?<realName>[^""]+?)\[\d+\](\.(?<suffix>\w{3,4}))?)"".+?title=""?(\k<realName>)?""?.+?alt=""?(\k<realName>)?""?.+?src=""file:///(\k<localAddrPref>/\k<realName>_thumb(\.\k<suffix>)?)""

            

从左往右,第一个是file:///后面的左括号,即对应的括号是?)"".+?title中的这个右括号,

由于其是unnamed的group,所以代码中的index为第一个,为1,

对应着代码和值是:

Groups[1].ToString() == 上述调试的结果 C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22[3].jpg

然后再往右数,第二个括号是(?<localAddrPref>中的左括号,对应右括号是+?)/(?<realName>中的右括号,

由于其实有名字的group,所以对应的index,不是Groups[2],而是等最后数完了全部的unnamed的group,才能知道此处的index。

但是由于是有名字的,所以也是可以直接通过group的name来获得对应的值的。

对应的代码和值是:

Groups["localAddrPref"] == C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15

然后按照此法:

从左往右,依次按照左括号出现的先后顺序去数,得到对应的各个group是:

  1. 第一个: ((?<localAddrPref>.+?WindowsLiveWriter.+?/supfile[^/]+?)/(?<lrealName>[^""]+?)\[\d+\](\.(?<lsuffix>\w{3,4}))?)

    unnamed index为1,值为Groups[1].ToString()

  2. 第二个: (?<localAddrPref>.+?WindowsLiveWriter.+?/supfile[^/]+?)

    有名字,index待定,值为Groups["localAddrPref"].Value

  3. 第三个: (?<realName>[^""]+?)

    有名字,index待定,值为Groups["realName"].Value

  4. 第四个: (\.(?<suffix>\w{3,4}))

    unnamed,index为2,值为Groups[2].ToString()

  5. 第五个:(?<suffix>\w{3,4})

    有名字,index待定,值为Groups["suffix"].Value

  6. 第六个: (\k<realName>)

    unnamed,index为3,值为Groups[3].ToString()

  7. 第七个: (\k<realName>)

    unnamed,index为4,值为Groups[4].ToString()

  8. 第八个: (\k<localAddrPref>/\k<realName>_thumb(\.\k<suffix>)?)

    unnamed,index为5,值为Groups[5].ToString()

  9. 第九个: (\.\k<suffix>)

    unnamed,index为6,值为Groups[6].ToString()

对照着上述debug出来的结果,正好分别就是:

  1. Groups[1].ToString() == debug中的[0] == C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22[3].jpg
  2. Groups[2].ToString() == debug中的[1] == .jpg
  3. Groups[3].ToString() == debug中的[2] == 王珞丹 图片22
  4. Groups[4].ToString() == debug中的[3] == 王珞丹 图片22
  5. Groups[5].ToString() == debug中的[4] == C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15/王珞丹 图片22_thumb.jpg
  6. Groups[6].ToString() == debug中的[5] == .jpg

而unnamed的全部都数完了,然后才是从左到右的,有名字的index依次增加,以及对应的值是:

  1. Groups["localAddrPref"].Value == Groups[7].ToString() == debug中的[6] == C:/Users/CLi/AppData/Local/Temp/WindowsLiveWriter1627300719/supfiles111AD15
  2. Groups["realName"].Value == Groups[8].ToString() == debug中的[7] == 王珞丹 图片22
  3. Groups["suffix"].Value == Groups[9].ToString() == debug中的[8] == jpg

所以,对于Regex中的group和named的group的index如何计算,就是上面那一句话:

从左到右,以左括号出现位置先后为顺序,index一次增加。

先全部数完unnamed的group,再去数有name的group,其中有name的group,完全可以不去计算对应的index是多少的,因为可以直接通过名字来引用对应的值,用法为:

Groups["yorGroupName"].Value