正则表达式基础

admin
admin 2019年10月28日
  • 在其它设备中阅读本文章

元字符:

  • .:匹配任意单个字符,但不能匹配换行符\n
  • *:匹配前面那个字符 0 或多次
  • ?:匹配前面那个字符 0 或一次
  • +:匹配前面那个字符 1 次以上
  • {M,N}:匹配前面那个字符至少 M,最多 N 次
  • {M,}:匹配前面那个字符至少 M 次,最多无限制
  • {,N}:匹配前面那个字符最多 N 次 (最少当然是 0 次)。某些语言可能不支持
  • {M}:匹配前面那个字符正好 M 次
  • 锚定:锚定的意思是匹配位置,而非匹配字符实体

    • ^:匹配行首位置,注意匹配的是位置,不是字符
    • $:匹配行尾位置,注意匹配的是位置,不是字符

特殊且常用的的组合正则表达式:

  • ^$:它表示匹配空行
  • .*:匹配任意长度的任意字符,但不能匹配换行符。真正的匹配任意长度的任意字符,见下面

需要解释清楚的是这些量词 (也就是上面匹配的次数元字符) 的特殊性:当使用了匹配多次的量词时(如匹配 3 - 5 次的{3,5}),且量词前面的字符有多种可能性 ( 如中括号序列[abc]),那么量词的次数可以作用于任一字符。有些不好理解,但看示例就知道了:

[abc]{3,5}     # 表示abc任意字符都可以出现,比如全是a,或者ab同时出现,但总的出现次数为3-5次
.*          # 表示任一字符(除换行符),可以任意出现任意次数,它不表示a之后就必须全是a
.+          # 表示任一字符(除换行符),可以任意出现至少一次,它不表示a之后就必须全是a

另外,.无法匹配换行符。可能你不太理解为什么需要匹配换行符,它主要用在:

  1. 多行模式。例如 sed 的多行模式下,要跨行匹配需要手动指定 "\n",如/^a.*\nb.*/
  2. 明确指定了行分隔符为非 "\n" 的情况。例如 awk 可以使用 RS 变量指定输入行分隔符

中括号

中括号表示的是匹配任意一个,一般它和字符集的排序规则有关,不同工具采取的排序规则可能也不一样。

  • [abcd...]:匹配中括号内的任意一个字符
  • [^abcd...]:拒绝匹配中括号内的任意字符
  • [a-z]:匹配字母 a 到 z
  • [A-Z]:匹配字母 A 到 Z
  • [0-9]:匹配 0 -9,也就是匹配数字

关于字母的排序:

  • perl 中,A- Z 排在 a 的前面,所以 [A-z] 表示所有大小写字母
  • grep 中,A- Z 排在 z 的后面,所以[a-Z] 表示所有大小写字母
  • 还有些工具中,大小写的排序规则是 aAbBcC...zZ,所以 [a-C] 表示 aAbBcC 共 6 个字母

字符类

是专门命名的中括号序列;除了字符类,还有等价类、排序类,但基本用不上,只用字符类。

  • [:alpha:]:匹配字母,等价于[a-zA-Z]
  • [:digit:]:匹配数字,等价于[0-9]
  • [:xdigit:]:匹配十六进制数,等价于[0-9a-fA-F]
  • [:upper:]:匹配大写字母,等价于[A-Z]
  • [:lower:]:匹配小写字母,等价于[a-z]
  • [:alnum:]:匹配数字或字母,等价于[0-9a-zA-Z]
  • [:blank:]:匹配空白,包括空格和制表符
  • [:space:]:匹配空格,包括空格、制表符、换行符、回车符等各种类型的空白
  • [:punct:]:匹配标点符号。包括:! ' " ` # $ % & ( ) * + , . - _ / : ; < = > ? @ [ \ ] ^ { | } ~
  • [:graph:]:绘图类。包括:大小写字母、数字和标点符号。等价于[:alnum:]+[:punct:]
  • [:print:]:打印字符类。包括:大小写字母、数字、标点符号和空格。等价于[:alnum:]+[:punct:]+space
  • [:cntrl:]:控制字符类。在 ASCII 中,这些字符的八进制代码从 000 到 037,还包括 177(DEL)

需要注意的是,通常字符类在真正使用过程中,会再加上一个中括号,例如[[:alpha:]]。之所以如此,是因为这些字符类只是一种命名好的字符集合。例如[:lower:]对应的字符集合是 a -z,而不是[a-z],所以要想让其表示这些命名字符类中的任一字符,需要再加上一层括号[[:lower:]],它才等价于[a-z]。可能会更有助于理解使用字符类的时候为什么要加两个中括号的例子是[^[:lower:]],它表示不包含任何小写字母。

反斜线序列

不同的工具,同一工具不同的版本,支持的反斜线序列能力不同。以下列出了部分常见序列。

以下所说的单词,一般来说只包含数字、字母和下划线,即[_0-9a-zA-Z]

以下几种反斜线序列,基本上所有工具都支持:

  • \b:匹配单词边界处的空字符
  • \B:匹配非单词边界处的空字符
  • \<:匹配单词开头处的空字符
  • \>:匹配单词结尾处的空字符
  • \w:匹配单词构成部分,等价于[_[:alnum:]]
  • \W:匹配非单词构成部分,等价于[^_[:alnum:]]

以下几种,有些工具不支持,但 perl 都支持:

  • \s:匹配空白字符,等价于[[:space:]]
  • \S:匹配非空白字符,等价于[^[:space:]]
  • \d:匹配数字,等价于[0-9]
  • \D:匹配非数字,等价于[^0-9]

由于元字符.默认无法匹配换行符,所以需要匹配换行符的时候,可以使用特殊组合[\d\D]来替换.,换句话说,如果想匹配任意长度的任意字符,可以换成[\d\D]*,当然,前提是必须支持\d\D两个反斜线序列。

分组捕获和反向引用

基础正则中,使用括号可以对匹配内容进行分组并暂时保存,分组后会有分组编号,可以使用反斜线加编号\N的方式反向引用这些分组。

分组编号的方式是从左向右计算括号数,无论如何嵌套,第一个左括号对应的分组一定是编号 1,用\1来引用,第二个左括号对应的分组一定是编号 2,用\2来引用,依此类推。

例如 grep 的分组捕获:匹配两个连续相同的字母。

echo "abcddefg" | grep -E "(.)\1"

可以认为分组就是变量赋值的过程。例如,上面示例的匹配过程如下:
1. 匹配第一个字母 a,放进分组,即赋值给变量 (假设变量名为 $1),即$1="a",再继续执行正则表达式匹配过程的反向引用,它引用的是 $1,于是表示第一个字母 a 后面还要是字母 a,于是匹配失败。
2. 匹配第二个字母 b,放进分组,即$1="b",再匹配后一个字母,于是匹配失败。
3. 字母 c 同样如此。
4. 匹配字母 d,放进分组,即$1="d",再匹配后一个字母,发现匹配成功,于是 $1 被保存下来。
5. 已经匹配成功,于是结束。

对于只使用基础正则的工具来说,一般都只能引用\1\9共 9 个反向引用,最多自己额外提供一个所有表示匹配内容的反向引用 ( 例如 sed 提供的&)。对于超出 10 个的分组,使用基础正则的工具一般来说是无能为力的。

再者,基础正则仅仅只是将分组匹配到的内容捕获,在正则操作结束后就丢失。但对于一门完整编程语言来说,这远远不够,几乎所有编程语言 (如 perl/java/python 等) 都会将正则的分组匹配内容保存为变量,使得可以在正则结束之后再次引用甚至修改它们。例如上面例子中分组捕获的字母 d,如果换成 perl,即使在这个匹配过程结束后,还是可以去引用这段分组。

二选一

  • pattern1 | pattern2:匹配竖线左边,或者匹配竖线右边都算匹配成功

关于二选一的结构,几点需要说明:

  1. 因为竖线元字符的优先级很低,所以ab|cd匹配的是 "ab" 或 "cd",而不是 abd 或 acd。
  2. 成功匹配了左边,就不会再去对右边进行匹配。
  3. 反向引用失败问题:竖线将两边的分组隔开,右边的永远无法反向引用左边的分组

在二选一结构种,两个反向引用问题的典型例子:

例如a(.)|b\1将无法匹配 "ba",因为评估了左边就不会评估右边。

例如([ac])e\1|b([xyz])\2t的左边能匹配 aea 或 cec,但不能匹配 cea 或 aec,右边能匹配 bxxt 或 byyt 或 bzzt。但如果将\2换成\1,即([ac])e\1|b([xyz])\1t,将无法匹配 b[xyz]at 或 b[xyz]ct,因为第一个分组括号在左边,无法参与右边的正则评估。