c#正则表达式

什么是正则表达式

很可能你使用过Windows/Dos下用于文件查找的通配符(wildcard),也就是和?。如果你想查找某个目录下的所有的Word文档的话,你会搜索.doc。在这里,*会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求——当然,代价就是更复杂

限定符

+:匹配一个或多个字符
*:匹配零个或多个字符
?:匹配零个或一个字符
{n}:匹配确定的n次
{n,}:至少匹配n次
{n,m}:最少匹配n次,最多匹配m次

定位符

  • ^:表示文本之首
  • $:表示文本末尾
  • \b:匹配单词边界
    虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置。假如你要找的是hi后面不远处跟着一个Lucy,你应该用\bhi\b.*\bLucy\b
  • \B:匹配非单词边界

元字符

.匹配任意字符

捕获组

捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。当然,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。
捕获组有两种形式,一种是普通捕获组,另一种是命名捕获组,通常所说的捕获组指的是普通捕获组。语法如下:

  • 普通捕获组:(Expression)
  • 命名捕获组:(?Expression)

另外需要说明的一点是,除(Expression)和(?Expression)语法外,其它的(?...)语法都不是捕获组。

普通捕获组:(pattern)

匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到

var reg = new Regex("(a)(b)\\1\\2");//  \\1\\2:引用第一第二个捕获组,即:a、b
var isMatch = reg.IsMatch("abab");//true
var matches = reg.Matches("abab");
var count = matches[0].Groups;//3   abab、a、b

命名捕获组(?pattern)

为捕获内容命名:(?pattern),等价于:?'name'pattern,只有.NET、PHP、Python等部分语言支持
引用捕获的文本:\k

var reg = new Regex("(?<name1>a)(?<name2>b)\\k<name1>\\k<name2>");
var isMatch = reg.IsMatch("abab");//true
var matches = reg.Matches("abab");
var count = matches[0].Groups;//3

非捕获性分组(?:pattern)

一些表达式中,不得不使用( ),但又不需要保存( )中子表达式匹配的内容,这时可以用非捕获组来抵消使用( )带来的副作用。

var reg = new Regex("(?:a)(b)\\1");
var isMatch = reg.IsMatch("abb");//true
var matches = reg.Matches("abb");
var count = matches[0].Groups;//2

捕获组的引用

对捕获组的引用一般有以下几种:
1)正则表达式中,对前面捕获组捕获的内容进行引用,称为反向引用;
2)正则表达式中,(?(name)yes|no)的条件判断结构;
3)在程序中,对捕获组捕获内容的引用。

反向引用

捕获组捕获到的内容,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。
普通捕获组反向引用:\k<number>,通常简写为\number。例如:\1\2\3...来表示第一、第二、第三组括号匹配的文本,在JScript中则使用$0…$9属性。
命名捕获组反向引用:\k<name>或者\k'name'

var reg = new Regex("(a)(b)\\1\\2");
var isMatch = reg.IsMatch("abab");//true
var matches = reg.Matches("abab");
var count = matches[0].Groups;//3

替换掉html标签中的属性。使用普通捕获组。

string data = "<table id=\"test\"><tr class=\"light\"><td> test </td></tr></table>";
var text = Regex.Replace(data, @"(?i)<([a-z]+)[^>]*>", "<$1>");
//输出
<table><tr><td> test </td></tr></table>

正向肯定预查(?=pattern)

当某个特定的字符分组出现在另一个字符串之前时才去匹配它。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,
“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

var reg = new Regex("windows(?=95|98|xp)");
var isMatch = reg.IsMatch("windows98,windowsxp,windows2000");//true
var matches = reg.Matches("windows98,windowsxp,windows2000");//windows、windows

反向肯定预查(?<=pattern)

反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。

正向否定预查(?!pattern)

与正向前瞻的区别是当特定字符不出现在另一字符前才去匹配它。这是一个非获取匹配。

案例1

“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符。

var reg = new Regex("windows(?!95|98|xp)");
var isMatch = reg.IsMatch("windows98,windowsxp,windows2000");//true
var matches = reg.Matches("windows98,windowsxp,windows2000");//windows

案例2

匹配以x开头, 后面不包含-, 的字符串. 写法如下:

^x((?!-).)*$

后面的((?!-).)*是什么意思呢?
x(?!-)意思就是x的后面没有:, 那么如果这么说的话, 他应该可以成功匹配形如xy, xx等字符串了? 并不是的. 注意上文维基百科解释中的最后一句话, 预查不消耗字符, 也就是说(?!-)只是表达了后面不能跟:, 但是并不代表xy中的y, 所以正确的表达式是形如x(?!-).x(?!-)\w这种, 让.\w来代表xy中的y,如下:
x123 :成功匹配
x123- :匹配失败

案例3

验证字符串中任意位置出现的连续四个数字在整个字符串中是否有重复,有重复为True,无重复为False

string[] test = new string[] { "1985aaa1985bb", "bcae1958aaa1955fef", "atijc1944cvkd", "df2564isdjfef2564d" };
Regex reg = new Regex(@"(\d{4})(?:(?!\1).)*\1");
foreach (string s in test)
{
      richTextBox2.Text += "源字符串:  " + s.PadRight(25, ' ') + "验证结果:  " + reg.IsMatch(s) + "\n";
}
/*--------输出--------
源字符串:  1985aaa1985bb            验证结果:  True
源字符串:  bcae1958aaa1955fef   验证结果:  False
源字符串:  atijc1944cvkd            验证结果:  False
源字符串:  df2564isdjfef2564d       验证结果:  True
*/

案例4

源字符串:string yourStr = "<aaa>bbb<abc>ccc<ddd>";
规则描述:取出yourStr中格式为<...>,但<>中不是abc的内容
预期结果:<aaa><ddd>
错误写法:<[^abc]*>
正确写法:<(?!abc>)[^>]*>

ASCII、Unicode表示字符

除了可以用字符来表示它们本身,也可以使用它们的ASCII代码或者Unicode代码指定字符。要使用ASCII来表示一个字符,则必须指定一个两位的十六进制数或八进制数,十六进制在前面加上\x,八进制在前加\。例如:字符b的ASCII码为98,等于十六进制的62,因此表示b可以使用\x62。如果要使用Unicode来表示字符,必须指定字符串的四位的十六进制形式。因此b应该是\u0062。

[\u4e00-\u9fa5]:匹配任意一个中文字符

RegexOptions

IgnoreCase:忽略大小写

Multiline:多行模式

使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。
Multiline模式下,^ 也匹配 '\n' 或 '\r' 之后的位置;$ 也匹配 '\n' 或 '\r' 之前的位置。

            var reg = new Regex("^[a-zA-Z]+$", RegexOptions.Multiline);
            var matches = reg.Matches("abc\ndef");
            var count = matches.Count;//2

Singleline:单行模式

            var reg = new Regex("^[a-zA-Z]+$", RegexOptions.Singleline);
            var matches = reg.Matches("abc\ndef");
            var count = matches.Count;//0

断言(环视)

在正则表达式中有如下四种断言(也称零宽度断言、环视):
(?<=Expression)逆序肯定环视,表示所在位置左侧能够匹配Expression
(?<!Expression)逆序否定环视,表示所在位置左侧不能匹配Expression
(?=Expression)顺序肯定环视,表示所在位置右侧能够匹配Expression
(?!Expression)顺序否定环视,表示所在位置右侧不能匹配Expression

顺序肯定环视

1.1、匹配指定内容的左侧位置
正则表达式:(?=\d{3})

(?=\d{3})表示校验的位置右侧是三个数字,所以匹配到的是c和1之间的位置。

1.2、匹配数字前的小写英文单词部分
正则表达式:([a-z]+)(?=\d+)

(?=\d+) 表示校验的位置右侧是数字。

1.3 校验字符串长度
正则表达式:(?=^.{5}$)(.*)

(?=^.{5}$)表示校验的位置右侧字符串长度为5,因为加了字符串开头和结尾的字符,所以这个表达式的作用是匹配长度为5的字符串。
如果将正则表达式写成:(?=^.{5}$),则匹配的是长度为5的字符串,其左侧的位置。

由上面例子可以看出,断言的作用就是指定一个位置,该位置的左侧(或右侧)内容需要满足Expression规则。由此可以推测出其他三种断言的使用方式。

顺序否定环视

(?!Expression),顺序否定环视,表示校验位置右侧的内容不匹配Expression规则。
示例的正则表达式:(?!.*\d{3}.*)(^.*$)

(?!.*\d{3}.*)表示校验位置右侧的内容中不包含三个连续的数字。

逆序肯定环视

(?<=Expression),逆序肯定环视,表示校验位置左侧的内容匹配Expression规则。

示例的正则表达式:(?<=\d{3})(.*$)

文本:abc123def
匹配到的内容:def

(?<=\d{3})表示校验位置左侧有三个连续的数字。这个表达式的作用是匹配三个连续数字右侧的内容。

逆序否定环视

((?<!Expression)),逆序否定环视,表示校验位置左侧的内容不匹配Expression规则。
示例的正则表达式:(?<!\d{2})([a-z]+)

文本:123def
匹配到的内容:ef

(?<!\d{2}),表示校验位置左侧不是两个连续的数字。这个表达式的作用是匹配字符串部分内容,这部分内容左侧不是两个连续的数字。

参考:
https://cloud.tencent.com/developer/article/2335067 (正则断言/环视)
https://blog.csdn.net/lxcnn?t=1
https://www.jianshu.com/p/8bf162425d83 (正向否定预查)
在线测试工具:
https://regex101.com/
http://c.runoob.com/front-end/854
https://tool.oschina.net/regex/

posted @ 2021-04-17 11:43  .Neterr  阅读(245)  评论(0编辑  收藏  举报