本文源码及示例代码位于
https://github.com/gehongyan/RegexTutorial
正则表达式
正则表达式,是一种文本模式,描述了一系列匹配某个句法规则的字符串。使用正则表达式进行的任务一般可以分为两类,即检索和替换。
正则表达式的语法
普通字符
普通字符,即不具有特殊含义的字符,如字母、数字、汉字等,它们在正则表达式中的含义与在普通文本中的含义相同,如在正则表达式中,a 代表字母 a,1 代表数字 1,你 代表汉字 你。
元字符
元字符,即具有特殊含义的字符。
| 元字符 | 含义 | 示例 | 
|---|---|---|
^ | 
匹配整个字符串的开始位置 | ^a 匹配 at,但不匹配 cat | 
$ | 
匹配整个字符串的结束位置 | i$ 匹配 hi,但不匹配 hit | 
. | 
匹配除换行符以外的任意字符 | a.c 匹配 abc,a c,a1c,a*c 等 | 
? | 
匹配前一个字符 0 次或 1 次 | hi? 匹配 h,hi,但不匹配 hii,hiii 等 | 
* | 
匹配前一个字符 0 次或多次 | hi* 匹配 h,hi,hii,hiii 等 | 
+ | 
匹配前一个字符 1 次或多次 | hi+ 匹配 hi,hii,hiii 等,但不匹配 h | 
{n} | 
匹配前一个字符 n 次 | hi{2} 匹配 hii,但不匹配 h,hi,hiii 等 | 
{n,} | 
匹配前一个字符 n 次或更多次 | hi{2,} 匹配 hii,hiii 等,但不匹配 h,hi 等 | 
{n,m} | 
匹配前一个字符 n 到 m 次 | hi{1,3} 匹配 hi,hii,hiii 等,但不匹配 h | 
 | 
匹配方括号中的任意字符 | [abc] 匹配 a,b,c,但不匹配 d,e,f | 
[^ ] | 
匹配除方括号中的任意字符 | [^abc] 匹配 d,e,f 等,但不匹配 a,b,c | 
( ) | 
用于匹配或捕获 | (abc) 匹配 abc,并将 abc 作为一个整体分组 | 
\| | 
用于分支选择 | a\|b 匹配 a 或 b,但不匹配 ab | 
字符转义
在正则表达式中,如果要匹配元字符本身,需要对元字符进行转义,即在元字符前加上反斜杠 \,使其被解释为普通字符,如 . 匹配除换行符以外的任意字符,如果要匹配 . 本身,则需要使用 \. 进行转义。
如果特殊字符出现在方括号 [] 中,则不需要转义,如 [.] 匹配 .,[*] 匹配 * 等。
如果要匹配反斜杠 \,则需要使用 \\ 进行转义。
如果有特殊字符不便于输入与显示,可以使用 \xhh 的形式进行转义,其中 hh 为 ASCII 码的十六进制表示,如 \x2e 转义为 .,\x66 转义为 f 等。也可以使用 \uXXXX 的形式进行转义,其中 XXXX 为 Unicode 码的十六进制表示,如 \u002e 转义为 .,\u0066 转义为 f 等。
还有一些预留的转义字符,以下是常用的转义字符:
| 转义字符 | 含义 | 等价于 | 
|---|---|---|
\n | 
换行符 | \x0a 或 \u000a | 
\r | 
回车符 | \x0d 或 \u000d | 
\t | 
水平制表符 | \x09 或 \u0009 | 
\v | 
垂直制表符 | \x0b 或 \u000b | 
\f | 
换页符 | \x0c 或 \u000c | 
字符类
字符类是一组字符的集合,用于匹配某个字符是否属于该字符类中的字符。常见的用法有:
| 用法 | 含义 | 示例 | 
|---|---|---|
 | 
匹配方括号中的任意字符 | [abc] 匹配 a,b,c,但不匹配 d,e,f | 
[^ ] | 
匹配除方括号中的任意字符 | [^abc] 匹配 d,e,f 等,但不匹配 a,b,c | 
. | 
匹配除换行符以外的任意字符 | a.c 匹配 abc,a c,a1c,a*c 等 | 
\w | 
匹配任意字母、数字或下划线 | \w 匹配 a,b,c,1,2,3,_ 等,但不匹配 .,-,+ 等,等价于 [a-zA-Z0-9_] | 
\W | 
匹配任意非字母、数字或下划线 | \W 匹配 .,-,+ 等,但不匹配 a,b,c,1,2,3,_ 等,等价于 [^a-zA-Z0-9_] | 
\d | 
匹配任意十进制阿拉伯数字 | \d 匹配 1,2,3,但不匹配 a,b,c,_ 等,等价于 [0-9] | 
\D | 
匹配任意非十进制阿拉伯数字 | \D 匹配 a,b,c,_ 等,但不匹配 1,2,3 等,等价于 [^0-9] | 
\s | 
匹配任意空白符(空格、制表符、换页符等) | \s 匹配空格,制表符,换页符等,但不匹配 a,b,c,1,2,3,_ 等,等价于 [ \t\n\r\f\v] | 
\S | 
匹配任意非空白符(空格、制表符、换页符等) | \S 匹配 a,b,c,1,2,3,_ 等,但不匹配空格,制表符,换页符等,等价于 [^ \t\n\r\f\v] | 
\p{} | 
Unicode 类别或命名块中的任意字符 | 匹配预定义的 Unicode 类别或命名块中的任意字符,如 \p{Lu} 匹配任意大写字母,\p{IsCJKUnifiedIdeographs} 匹配中日韩统一表意文字字符 | 
[base-[excluded]] | 
字符类减法,匹配 base 中的任意字符,但不匹配 excluded 中的任意字符 | 
[a-z-[aeiou]] 匹配任意小写字母,但不匹配任意元音字母,等价于 [bcdfghjklmnpqrstvwxyz] | 
定位点
| 用法 | 含义 | 示例 | 
|---|---|---|
^ | 
匹配整个字符串的开始位置 | ^a 匹配 at,但不匹配 cat | 
$ | 
匹配整个字符串的结束位置 | i$ 匹配 hi,但不匹配 hit | 
\b | 
匹配单词边界 | \babc 匹配 abc,abc123 中的 abc,但不匹配 123abc,123abc123 等 | 
\B | 
匹配非单词边界 | \Babc 匹配 123abc,123abc123 中的 abc,但不匹配 abc,abc123 等 | 
贪婪与懒惰
贪婪与懒惰是指正则表达式匹配时的匹配策略,贪婪是指尽可能多的匹配,懒惰是指尽可能少的匹配。
默认情况下,正则表达式是贪婪的,即尽可能多的匹配。如果要使用懒惰匹配,可以在量词后面加上 ?。
例如,针对文本 (abc)def(ghi),\(.*\) 尝试匹配括号及括号内的内容,但是由于是贪婪的,所以会尝试匹配最长的字符串,即 (abc)def(ghi),而不是 (abc) 或 (ghi)。如果要匹配 (abc) 或 (ghi),可以使用 \(.*?\)。
分组捕获
分组捕获是指将匹配到的字符串分组,以便后续使用。分组捕获的语法是使用 () 包裹要捕获的字符串,例如,针对文本 <p>text</p>,使用 <p>(.*?)</p> 可以将匹配结果捕获为一个分组,即 text。
捕获的分组可以进行反向引用,即在后续的正则表达式中使用 \1,\2,\3 等引用之前捕获的分组。例如,针对文本 <p>abc</p>,使用 <(.*?)>.*?</\1> 进行匹配,可以保证匹配出来的标签是成对的。
分组可以命名,语法为 (?<name>pattern),可以使用 \k<name> 进行反向引用。例如,针对文本 <p>abc</p>,使用 <(?<tag>.*?)>.*?</\k<tag>> 进行匹配,通过命名分组 tag,可以保证匹配出来的标签是成对的。
分组内的 | 表示或的关系,例如,针对文本 abc,使用 (a|b)c 可以匹配 ac 或 bc。
断言
断言是指在匹配时,不消耗字符,只判断是否匹配。
断言分为四种:正向肯定断言、正向否定断言、反向肯定断言、反向否定断言。
| 用法 | 含义 | 示例 | 
|---|---|---|
(?=pattern) | 
正向肯定断言,匹配 pattern 之前的位置 | abc(?=def) 匹配后面是 def 的 abc,匹配结果中不包含 def | 
(?!pattern) | 
正向否定断言,匹配不是 pattern 之前的位置 | abc(?!def) 匹配后面不是 def 的 abc | 
(?<=pattern) | 
反向肯定断言,匹配 pattern 之后的位置 | (?<=abc)def 匹配前面是 abc 的 def,匹配结果中不包含 abc | 
(?<!pattern) | 
反向否定断言,匹配不是 pattern 之后的位置 | (?<!abc)def 匹配前面不是 abc 的 def | 
例如,如要匹配括号及括号内的内容,前文使用的是 \(.*\),但是这样会匹配到括号。如果要在匹配结果中去掉括号,可以使用 (?<=\().*?(?=\))。
替换
替换是使用正则表达式的另一个常见用途,使用普通字符进行替换,则会将匹配的内容替换为指定的字符串。
例如,针对文本 <p>text</p>,使用 <(?<tag>.*?)>(.*?)</\k<tag>> 进行匹配,然后使用 result 进行替换,匹配结果为 result。
使用编号组引用语法,则可以对匹配的内容替换为编号捕获组,例如,对上面的文本使用 [$1] 进行替换,匹配结果为 [text]。
使用命名组引用语法,则可以对匹配的内容替换为命名捕获组,例如,对上面的文本使用 [${tag}] 进行替换,匹配结果为 [p]。
正则表达式选项
正则表达式选项是指在正则表达式中使用的一些选项,用于控制正则表达式的匹配策略。
| 选项 | 含义 | 
|---|---|
g | 
全局匹配,即匹配所有符合条件的字符串 | 
i | 
忽略大小写 | 
m | 
多行模式,即 ^ 和 $ 匹配每一行的开始和结束位置 | 
s | 
单行模式,即 . 匹配任意字符,包括换行符 | 
x | 
忽略空白字符,即空白字符不参与匹配 | 
正则表达式在 .NET 中的用法
正则表达式选项
RegexOptions 枚举定义了正则表达式选项,可以按位组合,常用的选项有:
| 选项 | 含义 | 
|---|---|
| None | 指定不设置任何选项 | 
| IgnoreCase | 指定不区分大小写的匹配 | 
| Multiline | 多行模式。 更改 ^ 和 $ 的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配 | 
| Singleline | 指定单行模式。 更改点 (.) 的含义,以使它与每个字符(而不是除 \n 之外的所有字符)匹配。 | 
| IgnorePatternWhitespace | 消除模式中的非转义空白并启用由 # 标记的注释 | 
| RightToLeft | 指定搜索从右向左而不是从左向右进行 | 
| Compiled | 指定正则表达式编译为中间语言代码,而不是解释 | 
常见方法
- IsMatch:指示正则表达式在输入字符串中是否找到匹配项。
 - Match:在输入字符串中搜索匹配正则表达式模式的子字符串,并将第一个匹配项作为单个 Match 对象返回。
 - Matches:在输入字符串中搜索正则表达式的所有匹配项并返回所有匹配。
 - Replace:在指定的输入字符串内,使用指定的替换字符串替换与某个正则表达式模式匹配的字符串。
 - Split:在由正则表达式匹配定义的位置将输入字符串拆分为一个子字符串数组。
 
调用方式
静态方法
Regex.IsMatch(Input, Pattern);
Regex.IsMatch(Input, Pattern, RegexOptions.Compiled);
实例方法
var regex = new Regex(Pattern);
var compiledRegex = new Regex(Pattern, RegexOptions.Compiled);
regex.IsMatch(Input);
compiledRegex.IsMatch(Input);
源代码生成器
.NET 7 中新增了正则表达式源代码生成器,可以通过正则表达式生成源代码,然后直接调用源代码中的方法。
[GeneratedRegex(Pattern, RegexOptions.Compiled)]
private static partial Regex MyRegex();
// 在方法中调用
bool isMatch = MyRegex().IsMatch(Input);
在 IDE 中的用法
Visual Studio 和 JetBrains Rider 等主流代码编辑工具或集成开发环境都是支持通过正则表达式进行搜索与替换的。
以替换双斜线代码注释为三斜线 XML 注释为例,可以使用以下的正则表达式进行搜索替换:
- 匹配:
(?<!/)//(?!/)\s*(?<comment>.+) - 替换:
/// <summary> ${comment} </summary> 
要替换为三行展开的 XML 注释,则可以使用以下的正则表达式进行搜索替换:
- 匹配:
(?<space> +)(?<!/)//(?!/)\s*(?<comment>.+) - 替换:
${space}/// <summary> ${space}/// ${comment} ${space}/// </summary> 
再以为所有的属性添加 JsonPropertyName 特性为例,可以使用以下的正则表达式进行搜索替换:
- 匹配:
(?<all>public\s(?!class)\S+\s(?<name>\S+)) - 替换:
[JsonPropertyName("{name}")]{all} 
实用工具
参考文献
Visits: 44