ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 第 26 章 正则表达式 **目录** [](ch26.html#id3119846) [简介](ch26s02.html) [运算优先级](ch26s03.html) [转义符](ch26s04.html) [字符类](ch26s05.html) [限定符](ch26s06.html) [贪婪与懒惰](ch26s06.html#id3120652) [分支条件](ch26s07.html) [分组、捕获](ch26s08.html) [分组](ch26s08.html#id3120847) [捕获](ch26s08.html#id3120909) [零宽断言](ch26s09.html) [负向零宽断言](ch26s09.html#id3121214) ## 简介 对于文本内容的处理,通常使用交互方式,手工调整;但如果你对源文本比较了解,则可以采用自动化的批量处理方式,这种方式效率高、迅速快 批量处理,要求根据一定规则,匹配源文本中的字符,转换为目标文本,这就要用到正则表达式 最简单的例子,使用`regular`进行匹配,结果如下: ``` `regular` expression ``` 正则表达式有许多变种:glob 表达式、基本正则表达式、perl 正则表达式、emacs 正则表达式…… 如[“通配符”一节](ch14s06.html#glob "通配符")中介绍的为最简单的 glob 表达式 ## 运算优先级 正则表达式与数学表达式的不同在于,数学表达式执行数学运算,而正则表达式执行字符运算;相同的是,它们都按一定的优先级进行运算 | 运算符 | 操作 | | --- | --- | | \ | 转义符 | | () | 捕获、匹配、断言 | | [] | 字符类 | | *+? | 限定符 | | {} | 范围 | | ^$ | 位置和顺序 | | &#124; | 或 | ## 转义符 如果源文本中出现了正则表达式中的运算符,如`(`,使用 `(` 无法匹配下列文本中的括弧,这时要使用 `\` 进行转义。用 `\(`匹配[[48](ch26s04.html#ftn.id3119995)]: ``` `(`regular expression) ``` 在文本中匹配[“运算优先级”一节](ch26s03.html "运算优先级")中的所有运算符,都要用这种形式: ``` \运算符 ``` 在文本中匹配`\`本身,要用 `\\` 非运算符前使用 `\` ,则有特殊的意义,例如`\n`匹配一个换行符。常用转义字符: | 转义字符 | 涵义 | | --- | --- | --- | | 常规匹配 | . | 匹配除换行符以外的任意字符 | | \w | 匹配字母或数字或下划线或汉字 | | \s | 匹配任意的空白符 | | \d | 匹配数字 | | \b | 匹配单词的开始或结束,在字符类里代表退格 | | ^ | 匹配字符串的开始,在字符类里表示”非“ | | $ | 匹配字符串的结束 | | 反向匹配 | \W | 匹配任意不是字母,数字,下划线,汉字的字符 | | \S | 匹配任意不是空白符的字符 | | \D | 匹配任意非数字的字符 | | \B | 匹配不是单词开头或结束的位置 | | [&#94;aeiou] | 匹配除了 aeiou 这几个字母以外的任意字符 | | 特殊字符 | \a | 报警字符(打印它的效果是电脑嘀一声) | | \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 | 当前搜索的开头 | * * * > [[48](ch26s04.html#id3119995)] 在 Emacs 和 Vim 正则表达式中正好反过来,使用`\(`表示分组,用`(`匹配字符 ## 字符类 要想匹配数字、字母、空白很容易,因为已经有了对应这些字符集合的转义符,但是如果你想匹配没有预定义的字符集合(比如元音字母 a、e、i、o、u),应该怎么办? 正则表达式中允许你自定义字符类,在方括号里列出它们就可以了 ``` [aeiou] ``` 预定义的字符集合,也可以用字符类表示,如 `\d` 等价于 `[0-9]` 有些运算符,在字符类中使用会有另一种意义,例如`^`表示“字符串开始”,但在字符类中却表示 “非”,以`expression`为例,使用`[exp]`匹配: ``` `exp`r`e`ssion ``` 使用`[^exp]`匹配(字符串中非 e、x、p 的字符): ``` exp`r`e`ssion` ``` 而使用`^[exp]`匹配(以 e、x 或 p 起始的字符串): ``` `e`xpression ``` ## 限定符 在上一小节中的表格中,我们知道 `.` 可以匹配除换行符以外的任意字符,使用`.`匹配下列文本: ``` expression ``` 但是`.`每次只匹配一个字符,如果想一次匹配多个,则要使用限定符 | 限定符 | 作用 | | --- | --- | | * | 匹配零次或多次 | | + | 匹配一次或多次 | | ? | 匹配零次或一次 | | {3} | 匹配三次 | | {3,5} | 匹配三到五次 | | {3,} | 匹配三次或以上 | 下面通过实例了解限定符的区别。 `es` 的匹配结果 ``` expr`es`sion ``` `es+` 的匹配结果(e,一个或多个 s) ``` expr`ess`ion ``` `es*` 的匹配结果(e,零或多个 s) ``` `e`xpr`ess`ion ``` `es?` 的匹配结果(e,零或一个 s) ``` `e`xpr`es`sion ``` ### 贪婪与懒惰 使用限定符进行匹配时,默认匹配尽可能多的字符。无论用 `.*` 还是 `.+` 匹配下列文本,都会匹配全部 ``` `expression` ``` 这种方式称为“贪婪模式”。在限定符之后加 `?` 则匹配尽可能少的字符,称为“懒惰模式”[[49](ch26s06.html#ftn.id3120698)] 例如,使用贪婪模式`a.+b`匹配: ``` `aaabab` ``` 使用懒惰模式`a.+?b`匹配: ``` `aaab`ab ``` * * * > [[49](ch26s06.html#id3120698)] `.+` 匹配一个或多个任意字符,在贪婪模式中,它匹配尽可能多的字符;而懒惰模式中(`.+?`),则只匹配一个字符;`.{3,5}`在贪婪模式中尽可能匹配5个字符,在懒惰模式中(`.{3,5}?`)只匹配3个字符;`?` 和 `*` 这样可以匹配零次的限定符,在懒惰模式下不匹配任何字符(`.*?`、`.??`) ## 分支条件 `|` 表示“或”,使用它进行分支选择 例如`[a-z]+|\d+`匹配单词或数字: ``` expression 123 ``` ## 分组、捕获 ### 分组 使用`(表达式)`对表达式进行分组,例如使用`(\d{3}\.){2}`匹配下面例子中的数字: ``` abc`123.456.`def ``` `\d{3}`表示三个数字,`(\d{3}\.)`表示三个数字加“`.`”为一组,`{2}`表示这一组内容重复两次 ### 捕获 在对表达式进行分组的时候,会捕获文本到自动命名的组里,使用 `\1 \2 ……` 后向引用组 例如用`([a-z]*)\ (\d*)`匹配下列文本,`([a-z]*)`为`\1`组,`(\d*)`为`\2`组 ``` kardinal 1234567 ``` 使用`\2\ \1`替换`([a-z]*)\ (\d*)`,可以改变两个字符串的顺序 ``` 1234567 kardinal ``` 如果分组较多,计数可能会不太方便,可以给分组指定名称,例如: ``` (?<**name**>[a-z]*)\ (?<**num**>\d*) \k<**num**>\ \k<**name**> (?#使用“`\k<name>`”后向引用) ``` 使用`(?:表达式)`,则只是分组,而不捕获,下面例子中,`(\d*)`为`\1`组 ``` (?:[a-z]*)\ (\d*) ``` ## 零宽断言 目前为止,我们学到的正则表达式匹配,都是有“宽度”的,使用 `\w+。` 匹配下面文本,会将 `。` 一同匹配: ``` regular。 expression。 ``` 如果不想匹配符号,只匹配一个位置,就要用到“零宽断言”(匹配宽度为零,满足一定的 条件/断言),零宽断言使用 **(?=表达式)** 的语法,例如 `\w+(?=。)`,其中 `(?=。)` 表示 `。` 前面的位置(先行断言) ``` `regular`。 `expression`。 ``` 如果需要匹配后面的位置,如: ``` 。`regular` 。`expression` ``` 则要用到后发断言 `(?&lt;=。)` ,使用 `(?&lt;=。)\w+` 得到上面的匹配结果 使用 `(?&lt;=&lt;b&gt;).*(?=&lt;/b&gt;)` 匹配标签中的内容 ``` <b>`粗体`</b> ``` ### 负向零宽断言 负向零宽断言 **(?!表达式)** 也是匹配一个零宽度的位置,不过这个位置的“断言”取表达式的反值,例如 `(?!表达式)` 表示 `表达式` 前面的位置,如果 `表达式` 不成立 ,匹配这个位置;如果 `表达式` 成立,则不匹配: ``` `expression` `expression`, `expression`; expression。 ``` 以上为使用 `.+n(?!。)` 的匹配结果。注意与 `.+n[^。]` 匹配的区别 ``` expression `expression,` `expression;` expression。 ``` 同样,负向零宽断言也有“先行”和“后发”两种,负向零宽后发断言为 **(?&lt;!表达式)** 使用 `(?&lt;![&lt;/])para(?!&gt;)` 匹配下面文本 ``` <para>`para`表示一个段落</para> ``` * `(?&lt;![&lt;/])` 表示 `para` 左边不能为 `&lt;` 或 `/` ;`(?!&gt;)` 表示 `para` 右边不能为 `&gt;`