学习正则表达式

Regular Expression

Posted by ywlvs on February 25, 2019

基本匹配

  • \< 表示锚定词首

  • \> 表示锚定词尾

  • \b 既能锚定词首,也能锚定词尾

  • \B\b 相反,\b 使用来匹配「单词边界」的,但是 \B 刚好相反

    比如有 morning 和 goodmorning 两个字符串,\bmorning 可以匹配到 morning,无法匹配到 goodmorning, 而 \Bmorning 匹配到的是 goodmorning,而不会匹配到 morning。

  • ^ 表示锚定行首

  • $ 表示锚定行尾

次数匹配

根据指定字符的连续出现次数进行匹配

  • {n} 表示指定的字符连续出现 n 次

  • {m,n} 字符连续出现的次数介于 m 和 n 之间

  • {,n} 字符连续出现的次数不高于 n,下限不做限制

  • {m,} 字符连续出现的次数不低于 m,上线不做限制

  • * 匹配任意长度的字符,包括长度为 0

abc* 能匹配 ab, abc, abcc, abccc 等。

  • . 匹配任意单个字符,换行符除外

如 ‘.’ 能匹配 a, b, c, d 等

  • .* 则用来匹配任意长度的任意字符

  • \? 匹配规则为:字符出现 0 次或者 1 次

  • \+ 匹配规则为:字符至少出现 1 次

  • [] 匹配范围内的任意单个字符

  • [^] 匹配范围外的单个字符

字符类型的匹配

  • [[:alpha:]] 匹配任意单个字母,不区分大小写,与 [a-zA-Z] 等效。

  • [[:digit:]] 匹配任意单个 0-9 的单个数字,与 [0-9] 等效。

  • [[:lower:]] 匹配任意单个小写字母,与 [a-z] 等效。

  • [[:upper:]] 匹配任意单个大写字母,与 [A-Z] 等效。

  • [[:alnum:]] 匹配任意单个字母或者数字,与 [a-zA-Z0-9] 等效。

  • [[:space:]] 匹配任意单个空白字符,包括「空格」「Tab制表符」等。

  • [[:punct:]] 匹配任意单个标点符号。

分组与后向引用

啥是分组

举个例子。

有个文档 list_1,我们查看一下内容。

root@Simona:~# cat -n list_1

    1  goodgoodverygood

    2  goodddgoodgood

    3  123412341234

比如想找到连续出现两次的 good 字符串,该如何操作呢?使用 \{n\} 可以进行次数匹配,但是它表示该匹配符前面的单个字符重复出现 n 次,因此使用 grep 'good\{2\}' list_1 无法实现我们的目标。

root@Simona:~# grep -n 'good\{2\}' list_1

2:goodddgoodgood

该命令匹配的是第 2 行的 goodddgoodgood

上面对内容的匹配,都是针对单个字符的。如果要匹配某个单词重复若干次,这个时候使用分组就对了。

root@Simona:~# grep -n '\(good\)\{2\}' list_1

1:goodgoodverygood

2:goodddgoodgood

使用上面的匹配模式,匹配结果是 goodgoodverygood 以及 goodddgoodgood

分组,就是将若干连续的字符视为一个整体作为匹配的对象。上面例子中,就是将 good 四个字符分成一组进行匹配的。

\(\) 就是分组匹配符,\(good\) 就是将 good 作为一组去匹配。

啥是后向引用

所谓后向引用,就是利用前面分组所匹配的对象,对后面的内容进行匹配。

举个例子。查看一下文件 list_2 的内容

root@Simona:~# cat -n list_2

    1  god & gun

    2  god & god

使用下面的匹配规则

root@Simona:~# grep -n '.\{3\} & .\{3\}' list_2

1:god & gun

2:god & god

可以看到,该模式会将两行都匹配出来。如果我们只想匹配第 2 行呢,就是说,对于该文本的每一行,要求 & 符号前后的字符串一致。这个时候可以使用后向应用进行匹配。看一看下面的例子。

root@Simona:~# grep -n '\(.\{3\}\) & \1' list_2

2:god & god

对于上面的匹配模式,\(.\{3\}\) 匹配了三个字符,并分组将其作为一个整体。接下来的 \1 就是后向应用的标志,表示引用前面第一个分组表达式匹配的结果。

\1 表示第 1 个分组的匹配结果,\2 表示第 2 个分组的匹配结果,\3 等以此类推。

对于多个分组的匹配模式,按照分组开始的位置进行排序。如 \(first\(second\)ok\),first 前面的对应的 ( 定义了第一个分组,second 前面的 ( 定义了第二个分组。

转义符

使用正则表达式对文本进行匹配的时候,用 . 表示来匹配一个字符(换行符除外)。那么,如果我们想匹配的是 . 这个字符本身,该如何做呢?同理,想匹配 *, 这种符号,又该怎么处理呢?这就涉及到转义符了。

\ 就是转义符。

如果想匹配 .,使用 \. 进行转义;同理,想匹配 *,使用 \* 进行转义。

看看例子,先查看一下文本的内容。

root@Simona:~# cat -n list_3

     1  a..

     2  a**

     3  a*#

     4  a(*

我们想使用 a.. 来匹配第一行,实际情况是,. 可以匹配任意一个单个字符,因此,被匹配到的不仅仅是 「a..」,「a**」 和 「a*#」 等也被匹配到了。

root@Simona:~# grep -n 'a..' list_3

1:a..

2:a**

3:a*#

4:a(*

接下来,我们使用转义符进行转义。

root@Simona:~# grep -n 'a\.\.' list_3

1:a..

可以发现,在使用了 \ 进行转义之后,只有第 1 行匹配成功。同理,如果想匹配 * 本身,使用 \* 进行转义匹配。对于 ( 这种标点,直接使用 ( 进行匹配就可以了。

对于上面的文本,我们想匹配第 4 行的 「a(*」。

root@Simona:~# grep -n 'a(\*' list_3

4:a(*

在 shell 中通过 grep 工具使用正则

环境配置

+ Ubuntu 16.04.2

+ /bin/bash

使用 grep 进行正则匹配的时候,正则表达式可以用单引号 '' 包裹起来,不要使用双引号 "",否则可能有惊喜。

使用示例

  • 匹配一个 uuid 字符串
echo 9ea18301-3b7e-8e72-2566-7856cb543762 | grep '^[[:alnum:]]\{8\}-[[:alnum:]]\{4\}-[[:alnum:]]\{4\}-[[:alnum:]]\{4\}-[[:alnum:]]\{12\}$'

字符匹配规则的简写

  • \d 表示任意单个0到9的数字

  • \D 表示任意单个非数字字符

  • \t 表示匹配单个横向制表符(相当于一个tab键)

  • \s 表示匹配单个空白字符,包括「空格」,「tab制表符」等

  • \S 表示匹配单个非空白字符

扩展正则表达式

在 Linux 中,grep 工具默认使用「基本正则表达式」去匹配字符串,如果要使用「扩展正则表达式」,则需要加上 -E 选项。

在使用上,基本的匹配规则是通用的,相比于「基本正则表达式」,「扩展正则表达式」更加简洁。举两个例子, \{\}{} 对应,\(\)() 对应,可以看到,不用再写转义符 \ 了,表达式更简洁,语义化也更好。

「扩展正则表达式」中,有一个「基本正则表达式」没有的匹配符,就是 | 符号,用来表示「或」的关系。看例子:

root@Simona:~# cat -n list_4

     1  www.simona.xyz

     2  www.baidu.com

     3  www.coding.net
root@Simona:~# grep -nE '(net|com)$' list_4

2:www.baidu.com

3:www.coding.net