Linux基础系列文章大纲
Shell系列文章大纲


基础正则表达式

本文介绍基础正则表达式,没有示例,只有总结,如果想学习更强大、更完整的正则,可以参考:Perl正则超详细教程grep -P、ack都支持Perl正则,且很多需要使用到正则的服务软件一般都采用PCRE(如httpd、nginx、haproxy、proxysql),它和Perl正则几乎完全一致。

元字符:

  • .:匹配任意单个字符,但不能匹配换行符\n
  • *:它前面的正则表达式需执行0或多次匹配操作
  • ?:它前面的正则表达式需执行0或1次匹配操作
  • +:它前面的正则表达式需执行1次以上的匹配操作
  • {M,N}:它前面的正则表达式需执行至少M次至多N次匹配操作
  • {M,}:它前面的正则表达式需执行至少M次匹配操作
  • {,N}:它前面的正则表达式需执行至多N次匹配操作(最少当然是0次)。注意,perl正则不支持这种方式
  • {M}:它前面的正则表达式需执行M次匹配操作
  • 锚定:锚定的意思是匹配位置,而非匹配字符实体
    • ^:匹配行首位置,注意匹配的是位置,不是字符
    • $:匹配行尾位置,注意匹配的是位置,不是字符

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

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

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

1
2
3
[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的分组捕获:匹配两个连续相同的字母。

1
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匹配的是abcd,而不是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]atb[xyz]ct,因为第一个分组括号在左边,无法参与右边的正则评估。