正则表达式(Regular Expression,通常简称为 regex 或 RE)是一种表达方式,可以用它来查找匹配特定准则的文本。在许多编程语言中都有用到正则表达式,常用它来实现一些复杂的匹配。这里简单介绍一下 shell 中常用到的一些正则表达式。
一、什么是正则表达式
正则表达式是对字符串进行操作的一种逻辑公式,即用事先定义好的的一些特定字符以及这些特定字符的组合,组成一个有一定规则的字符串(Regular Expression),使用这个有一定规则的字符串来表达对字符串的一种过滤逻辑。正则表达式被广泛应用于Linux和许多其他编程语言中,而且不论在哪里,其基本原理都是一样的。
从根本上来看,正则表达式是由两个基本组成部分所建立:一般字符与特殊字符。一般字符是指没有任何特殊意义的字符;特殊字符,常称为元字符 (metacharacter),或 meta 字符,顾名思义,就是指那些有特殊意义的字符,当然在某些情况下,特殊字符也可被视为一般字符(使用转义符 \ 进行转义)。
POSIX 有两种风格的正则表达式,基本正则表达式(BRE)和扩展正则表达式(ERE)。这两种风格的正则表达式在一些字符含义上有细微的差距。以常用的 grep 指令来说,grep 指令默认支持的是 BRE,若要使用 ERE 进行匹配,可以使用 -E 选项,接下来的例子中均使用 grep 指令来演示正则表达式的使用。
二、基本正则表达式
2.1 常用 meta 字符列表
字符 | BRE/ERE | 含义 |
. | BRE&ERE | 匹配任意单个字符(除字符串结束符 NUL) |
^ | BRE&ERE | 匹配行首,如 ^abc,匹配以 abc 开头的字符串 |
$ | BRE&ERE | 匹配行尾,如 abc$,匹配以 abc 结尾的字符串 |
* | BRE&ERE | 匹配 0 个或任意多的单个字符,前置字符可以是正则表达式 |
+ | ERE | 匹配前面正则表达式的 1 个或多个实例 |
? | ERE | 匹配前面正则表达式的 0 个或 1 个实例 |
[...] | BRE&ERE | 方括号表达式,匹配方括号内的任一字符,常配合 - 符使用,表示匹配一个连续的范围。 ^ 字符 作为方括号内的第一个字符表示匹配不在方括号内的任意字符 |
- | BRE&ERE | 连字符,在方括号表达式中使用,表示连续字符的范围(范围会因 locale 而有所不同,因此不具可移植性) |
{n,m} | ERE | 区间表达式,表示匹配在它前面的字符 n 到 m次。其中,n 与 m 的值必须介于 0-RE_DUM _MAX(含)之间,后者最小值为255 |
{n} | ERE | 表示匹配在这之前的字符 n 次 |
\{n,m\} | BRE | 功能同 {n,m} |
\{n\} | BRE | 功能同{n} |
\ | BRE&ERE | 转义符 |
() | ERE | 匹配位于方括号括起来的正则表达式群 |
\( \) | BRE | 将\( 与 )\ 之间的模式保存在特殊的“保留空间”中,最多可以存储9个,可以通过后续的转义序列 \n 来匹配保留空间中的模式 |
\n | BRE | 与 \( \) 结合起来使用,\1匹配第一个子模式、 \2匹配第二个,最多到 \9 |
| | ERE | 匹配位于 | 符号前或后的正则表达式 |
接下来,笔者将使用 grep 命令对 /etc/passwd 文件进行查找匹配操作:
1)匹配一般字符:
[tongye@localhost ~]$ grep root /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin
2)使用点字符 " . " 匹配任意字符:
[tongye@localhost ~]$ grep r..t /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
3)使用星号字符 " * " 或问号字符 " ? "匹配0个或多个字符:
[tongye@localhost ~]$ grep roo* /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin chrony:x:998:996::/var/lib/chrony:/sbin/nologin
grep roo* /etc/passwd 命令将在 /etc/passwd 中匹配 ro ,后面可以接 0 个或多个 o。在 ERE 风格下,使用的是 ? 符号来达到和 * 号一样的效果:
[tongye@localhost ~]$ grep -E roo? /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin chrony:x:998:996::/var/lib/chrony:/sbin/nologin
4)使用加字符 " + " 匹配1个或多个字符:
[tongye@localhost ~]$ grep -E roo+ /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin [tongye@localhost ~]$ grep -E ro+ /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin chrony:x:998:996::/var/lib/chrony:/sbin/nologin
使用 + 字符可以匹配在其前面的 1 个或多个字符,与 * 字符有些许的差别,另外, + 字符实在 ERE 风格下使用的,故需要使用 grep 命令的 -E 选项
5)使用 ^ 匹配行首,$匹配行尾:
[tongye@localhost ~]$ grep ^t /etc/passwd tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin tongye:x:1000:1000:tongye:/home/tongye:/bin/bash [tongye@localhost ~]$ grep ^t.*h$ /etc/passwd tongye:x:1000:1000:tongye:/home/tongye:/bin/bash
.* 结合在一起表示匹配零个或多个任意字符,与 ^ 和 $ 结合起来使用的话就可以匹配一个指定开头和结尾的字符串了
6)使用方括号表达式匹配括号内的任一字符:
[tongye@localhost ~]$ grep [Nn]et /etc/passwd systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
结合 - 字符使用,可以表示匹配一个范围内的任一字符,如 [0-9] 表示匹配 0-9 中的任意一个数字、[a-z] 表示匹配一个小写字母、[A-Z] 表示匹配一个大写字母:
[tongye@localhost ~]$ grep [a-z]c /etc/passwd sync:x:5:0:sync:/sbin:/bin/sync tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin abrt:x:173:173::/etc/abrt:/sbin/nologin
结合 ^ 字符使用,表示取反
[tongye@localhost ~]$ grep [^a-z]c /etc/passwd tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin chrony:x:998:996::/var/lib/chrony:/sbin/nologin
这里表示匹配一个非小写字母的字符后接一个字符 c 的字符串。注意,^ 放在方括号里面表示反向含义,放在方括号外面则表示的是匹配行首。
7)使用 {n.m} 区间表达式来匹配指定的次数:
这个表达式可以用来匹配指定的次数,其中 {n,m} 表示匹配在其前面的字符 n 到 m次,{n,} 表示至少匹配 n 次,{,m} 表示最多匹配 m 次,而 {n} 则是精准匹配 n 次。在 BRE 中,使用的是 \{n,m\} 的形式来实现相同的功能。n 与 m 的值必须介于 0 至 RE_DUP_MAX(包含这个值)之间,后者的最小值为255
[tongye@localhost ~]$ grep 0'\{3\}' /etc/passwd tongye:x:1000:1000:tongye:/home/tongye:/bin/bash [tongye@localhost ~]$ grep -E 0{3} /etc/passwd tongye:x:1000:1000:tongye:/home/tongye:/bin/bash
8)使用 \( \) 保存已匹配的字符,并通过 \n 来引用已保存的匹配字符串
使用 \( \) 会先匹配括号中的字符串,然后将匹配到的字符串保存在由正则表达式解析器预定义好的叫做寄存器的变量中,其编号从1到9,也就是说最多可以保存9组字符串,使用 \n 可以取出所保存的字符串,其中 n 为1到9,分别对应9个寄存器的值。
[tongye@localhost ~]$ grep '\(operator\).*\1' /etc/passwd operator:x:11:0:operator:/root:/sbin/nologin
关于这个表达式还有一个有意思的用法:
[tongye@localhost ~]$ grep '^\(.\).*\1$' /etc/passwd nobody:x:99:99:Nobody:/:/sbin/nologin
如上,正则表达式 ^\(.\).*\1$ 将匹配一个行首字符和行尾字符相同的字符串。
2.2 POSIX 方括号表达式
为了配合非英语的环境,POSIX 标准强化其字符集范围的能力 (如 [a-z]),以匹配非英文字母字符。POSIX 使用方括号表达式 [...] 来表示一个范围值,在方括号表达式里,除了字面上的字符外(a、b、c等),另有额外的组成部分,包括:
1)字符集:以 [: ... :] 将关键字组合括起来的 POSIX 字符集,关键字描述各种不同的字符集;
2)排序符号:排序符号将多个字符序列视为一个单位(如,locale 中将 ch 这两个字符视为一个单位),它使用 [. 与 .] 将字符组合括起来,在系统所使用的特定 locale 上各有其定义;
3)等价字符集:等价字符集列出的是应视为等值的一组字符,它由取自于 locale 的名字元素组成,以 [= 与 =] 括住。
下表是 POSIX 字符集列表:
类别 | 匹配字符 |
[:alnum:] | 数字字符 |
[:alpha:] | 字母字符 |
[:blank:] | 空格与定位符 |
[:cntrl:] | 控制字符 |
[:digit:] | 数字字符 |
[:graph:] | 非空格字符 |
[:lower:] | 小写字母字符 |
[:upper:] | 大写字母字符 |
[:space:] | 空白符 |
[:print:] | 可显示的字符 |
[:punct:] | 标点符号字符 |
[:xdigit:] | 十六进制数字 |
需要注意的是,上述字符集也是要放到方括号表达式中去的,因此一般会出现类似 [[:alpha:]] 的表达式。
举个例子:
[tongye@localhost ~]$ grep [[:upper:]] /etc/passwd ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin polkitd:x:999:998:User for polkitd:/:/sbin/nologin tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
该正则表达式匹配所有的大写字母。
参考资料:
《Linux程序设计 第四版》
《Shell 脚本学习指南》
《UNIX/Linux/OS X 中的 Shell 编程 第四版》