正则表达式可以看作通配符 * 的扩展,可以匹配任何符合要求的字段。
它很有用,然而可读性并不好,所以需要在线测试参考文档的帮助。

元字符

元字符匹配一个位置,它可以是一个字符实体,也可以是某个边界。
在正则中,单词的含义是任意多连续的 \w

.	匹配除换行符以外的任意字符
\w	匹配字母或数字或下划线或汉字
\s	匹配任意的空白符
\d	匹配数字
\b	匹配单词的开始或结束
^	匹配字符串的开始
$	匹配字符串的结束

示例:

0\d\d-\d\d\d\d\d\d\d\d	以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字,也就是中国的电话号码

重复

在元字符的基础上进行重复,到这个时候已经能应付很多查找情况了。

*	重复零次或更多次
+	重复一次或更多次
?	重复零次或一次
{n}	重复n次
{n,}	重复n次或更多次
{n,m}	重复n到m次

示例:

Windows\d+	匹配Windows后面跟1个或更多数字
^\w+	匹配一行的第一个单词(或整个字符串的第一个单词,具体匹配哪个意思得看选项设置)

字符类

方括号的应用:

[aeiou]	匹配任何一个英文元音字母
\(?0\d{2}[) -]?\d{8}	匹配几种格式的电话号码

分支条件

或运算符 | 的应用:

\d{5}-\d{4}|\d{5}	匹配5位数字,或者用连字号间隔的9位数字

分组

正则不提供任何关于数学的功能,所以只能通过冗长的分组来表示复杂的内容。

((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)	一个正确的IP地址

反义

反义即取开始所说一些元字符的补集。

\W 	匹配任意不是字母,数字,下划线,汉字的字符
\S 	匹配任意不是空白符的字符
\D 	匹配任意非数字的字符
\B 	匹配不是单词开头或结束的位置
[^x] 	匹配除了x以外的任意字符
[^aeiou] 	匹配除了aeiou这几个字母以外的任意字符

示例:

\S+	匹配不包含空白符的字符串。
<a[^>]+>	匹配用尖括号括起来的以a开头的字符串

后向引用

这里开始变得有些生涩了,后向引用用于重复搜索前面某个分组匹配的文本:

\b(\w+)\b\s+\1\b	匹配重复的单词

常用分组语法:

捕获 	(exp)	匹配exp,并捕获文本到自动命名的组里
	(?<name>exp) 	匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
	(?:exp) 	匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言	(?=exp) 	匹配exp前面的位置
	(?<=exp) 	匹配exp后面的位置
	(?!exp) 	匹配后面跟的不是exp的位置
	(?<!exp) 	匹配前面不是exp的位置
注释 	(?#comment) 	这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

零宽断言

零宽断言用于匹配那些有所属特征字符的另外部分:

\b\w+(?=ing\b)	匹配以ing结尾的单词的前面部分
(?<=\bre)\w+\b	匹配以re开头的单词的后半部分

负向零宽断言

负向零宽断言只匹配而不消费任何字符:

\b\w*q(?!u)\w*\b	后面不是字母u的字母q的单词(解决q出现在单词结尾不被匹配的问题)

注释

注释可以让正则对人类友好一点,可以启用【忽略模式里的空白符】以达到比较好的显示效果。

(?<=    # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的内容
        # (即HTML/XML标签)
)       # 前缀结束
.*      # 匹配任意文本
(?=     # 断言要匹配的文本的后缀
<\/\1>  # 查找尖括号括起来的内容
        # 查找尖括号括起来的内容
)       # 后缀结束

贪婪与懒惰

选取尽可能多和尽可能少的匹配。

*? 	重复任意次,但尽可能少重复
+? 	重复1次或更多次,但尽可能少重复
?? 	重复0次或1次,但尽可能少重复
{n,m}? 	重复n到m次,但尽可能少重复
{n,}? 	重复n次以上,但尽可能少重复

示例:

a.*b	匹配最长的以a开始,以b结束的字符串
a.*?b	匹配最短的,以a开始,以b结束的字符串

处理选项

选项用来改变一些含义,.Net 中的几个选项:

IgnoreCase(忽略大小写) 	匹配时不区分大小写。
Multiline(多行模式) 	更改^和$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.)
Singleline(单行模式) 	更改.的含义,使它与每一个字符匹配(包括换行符\n)。
IgnorePatternWhitespace(忽略空白) 	忽略表达式中的非转义空白并启用由#标记的注释。
ExplicitCapture(显式捕获) 	仅捕获已被显式命名的组。

平衡组/递归匹配

套娃匹配。

(?'group')	把捕获的内容命名为group,并压入堆栈(Stack)
(?'-group')	从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
(?(group)yes|no)	如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
(?!)	零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败

示例:

<                   #最外层的左括号
  [^<>]*            #它后面非括号的内容
  (
      (
        (?'Open'<)  #左括号,压入"Open"
        [^<>]*      #左括号后面的内容
      )+
      (
        (?'-Open'>) #右括号,弹出一个"Open"
        [^<>]*      #右括号后面的内容
      )+
  )*
  (?(Open)(?!))     #最外层的右括号前检查
                    #若还有未弹出的"Open"
                    #则匹配失败

>                #最外层的右括号

<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.	匹配嵌套的<div>标签

剩余的部分

关于运算的优先级,正则是相同优先级的从左到右进行运算,不同优先级的运算先高后低。

以及额外需要了解的内容:

\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

总结

正则表达式对人类极度不友好,但奈何它有用。
从元字符到反义都很好理解,而后面的分组就需要更多消化了。

参考文章:https://deerchao.cn/tutorials/regex/regex.htm