第 26 章 正则表达式

目录

简介

运算优先级

转义符

字符类

限定符

贪婪与懒惰

分支条件

分组、捕获

分组

捕获

零宽断言

负向零宽断言

简介

对于文本内容的处理,通常使用交互方式,手工调整;但如果你对源文本比较了解,则可以采用自动化的批量处理方式,这种方式效率高、迅速快

批量处理,要求根据一定规则,匹配源文本中的字符,转换为目标文本,这就要用到正则表达式

最简单的例子,使用regular进行匹配,结果如下:

`regular` expression

正则表达式有许多变种:glob 表达式、基本正则表达式、perl 正则表达式、emacs 正则表达式……

“通配符”一节中介绍的为最简单的 glob 表达式

运算优先级

正则表达式与数学表达式的不同在于,数学表达式执行数学运算,而正则表达式执行字符运算;相同的是,它们都按一定的优先级进行运算

运算符 操作
\ 转义符
() 捕获、匹配、断言
[] 字符类
*+? 限定符
{} 范围
^$ 位置和顺序
|

转义符

如果源文本中出现了正则表达式中的运算符,如(,使用 ( 无法匹配下列文本中的括弧,这时要使用 \ 进行转义。用 \(匹配[48]:

`(`regular expression)

在文本中匹配“运算优先级”一节中的所有运算符,都要用这种形式:

\运算符

在文本中匹配\本身,要用 \\

非运算符前使用 \ ,则有特殊的意义,例如\n匹配一个换行符。常用转义字符:

转义字符 涵义
常规匹配 . 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束,在字符类里代表退格
^ 匹配字符串的开始,在字符类里表示”非“
$ 匹配字符串的结束
反向匹配 \W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^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] 在 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]

例如,使用贪婪模式a.+b匹配:

`aaabab`

使用懒惰模式a.+?b匹配:

`aaab`ab

[49] .+ 匹配一个或多个任意字符,在贪婪模式中,它匹配尽可能多的字符;而懒惰模式中(.+?),则只匹配一个字符;.{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;/])para(?!&gt;) 匹配下面文本

<para>`para`表示一个段落</para>
  • (?&lt;![&lt;/]) 表示 para 左边不能为 &lt;/(?!&gt;) 表示 para 右边不能为 &gt;