第 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`
则要用到后发断言 (?<=。)
,使用 (?<=。)\w+
得到上面的匹配结果
使用 (?<=<b>).*(?=</b>)
匹配标签中的内容
<b>`粗体`</b>
负向零宽断言
负向零宽断言 (?!表达式) 也是匹配一个零宽度的位置,不过这个位置的“断言”取表达式的反值,例如 (?!表达式)
表示 表达式
前面的位置,如果 表达式
不成立 ,匹配这个位置;如果 表达式
成立,则不匹配:
`expression`
`expression`,
`expression`;
expression。
以上为使用 .+n(?!。)
的匹配结果。注意与 .+n[^。]
匹配的区别
expression
`expression,`
`expression;`
expression。
同样,负向零宽断言也有“先行”和“后发”两种,负向零宽后发断言为 (?<!表达式)
使用 (?<![</])para(?!>)
匹配下面文本
<para>`para`表示一个段落</para>
(?<![</])
表示para
左边不能为<
或/
;(?!>)
表示para
右边不能为>