了解POSIX扩展正则表达式
本文将简单介绍POSIX扩展正则表达式。
什么是POSIX扩展正则表达式
POSIX扩展正则表达式,也称ERE(Extended Regular Expressions)是基于BRE的扩展功能,目的是为正则表达式提供更加丰富的功能,和更加简洁的写法。
如何启用扩展正则表达式呢?
在grep 中,需要使用-E 选项来启用扩展功能,sed 也是同理,需要添加-E来启用扩展,而awk 默认就支持该扩展功能。
新增、修改的点
取消了转义符
在ERE中,BRE需要转义的元字符,在ERE中不需要再进行转义了,比如() 、{} 等。
新增的元字符
在ERE中,新增了+ 和?的元字符,其含义是:
+: 匹配前面的元素1次或者多次。
?: 匹配前面的元素0次或者1次。
操作
直接引用不需要转义
在ERE中,可以直接引用{} 、() 中,而不需要进行转义,简化了写法,如:
| Bash |
|---|
| wangli@debian:~$ echo "worrrrld" | grep -E 'r{1,3}'
worrrrld
wangli@debian:~$
|
| Bash |
|---|
| wangli@debian:~$ echo "worrrld" | sed -E 's/(r{3})/-\1-/'
wo-rrr-ld
wangli@debian:~$
|
在BRE 中,需要将{} 和() 进行转义后,才能使用,写出来,可读性大大降低,在ERE中,直接调用即可,可读性增强了。
同样的,如果只想表示{} 、() 本身的含义,需要用\ 进行转义,如:
| Bash |
|---|
| wangli@debian:~$ echo "2{35}" | grep -E '2{35}'
wangli@debian:~$
wangli@debian:~$ echo "2{35}" | grep -E '2\{35\}'
2{35}
wangli@debian:~$
|
新增的元字符
在ERE中,新增了? 和+ 2个元字符,前者表示匹配前面的字符0次或者1次,后者表示匹配前面的字符1次或多次,这其实也是简化了BRE的写法,案例如下:
| Bash |
|---|
| wangli@debian:~$ echo "1123" | grep -E '111?'
1123
wangli@debian:~$
wangli@debian:~$ echo "1123" | grep "111\{0,1\}"
1123
wangli@debian:~$
|
这里需要介绍下,为什么1123 可以使用111? 来获取到呢?首先? 的含义是表示匹配前面的字符0次或者1次,所以111? 会有2中情况:
11 ,再匹配一个1 ,结果是111
11,不匹配后面的1 ,结果是11
所以最后会被匹配到,同样的,使用BRE来写的话,表达式为 111\{0,1\},然后功能完全一样,但是非常不直观。
再来看看+ 呢。
| Bash |
|---|
| wangli@debian:~$ echo "1111111123" | sed -E 's/(1+)/\1-/'
11111111-23
wangli@debian:~$
wangli@debian:~$ echo "1111111123" | sed 's/\(1\{1,\}\)/\1-/'
11111111-23
wangli@debian:~$
|
通过上面2个语句,可以发现虽然s/(1+)/\1-/ 和s/\(1\{1,\}\)/\1-/ 效果都是一样的,但是第一种要简单、直观很多,可读性要比第二种好。
一些关于正则表达式的例子
剔除多余的空格
有以下文本:
| Bash |
|---|
| wangli@debian:~$ cat test.txt
grep prints
lines that contain a match for one or more patterns.
wangli@debian:~$
|
需要将多余的空格给剔除掉。
| Bash |
|---|
| wangli@debian:~$ cat test.txt | sed -E -e 's/ +/ /g' -e 's/^ +//g'
grep prints
lines that contain a match for one or more patterns.
wangli@debian:~$
|
使用sed -e 可以执行多条命令,s/ +/ /g 表示将包含了多个连续空格都替换为1个空格,s/^ +//g 表示将以空格开头的连续空格都替换为空,即删除以空格开头的连续空格。
删除注释
定义注释的含义
| Text Only |
|---|
| # 这也是注释
# 这也是一种注释 #
# 这也是一种注释 # 这是正常的内容
|
有如下内容:
| Bash |
|---|
| wangli@debian:~$ cat test.txt
# 这也是注释
# 这也是一种注释 #
# 这也是一种注释 # 这是正常的内容
a b e d # 含义是 a b c d
c d e f # 这是注释 # g h i j k
a # 注释 # b
# 这也是注释
# 这还是注释 # ccccc
ddd
aa
wangli@debian:~$
|
去除掉注释
| Bash |
|---|
| wangli@debian:~$ cat test.txt | sed -E -e 's/#.*#//' -e 's/#.*//'
这是正常的内容
a b e d
c d e f g h i j k
a b
ccccc
ddd
aa
wangli@debian:~$
|
提取并且交换字符串中的两个数字
有以下文本:
| Bash |
|---|
| wangli@debian:~$ cat test.txt
a 123 b c d e 456 f
wangli@debian:~$
|
需要将123和456顺序交换。
| Bash |
|---|
| wangli@debian:~$ cat test.txt | sed -E -n 's/(.* )([0-9]{1,5})( .* )([0-9]{1,5})/\1\4\3\2/p'
a 456 b c d e 123 f
wangli@debian:~$
|
上面正则含义为:
(.* ): 表示匹配以任何字符开头且任何长度的字符,并且以空格结束,并且设为分组1。
([0-9]{1,5}): 表示匹配1-5为数字,并且设置为分组2。
( .* ):表示匹配以空格开头和空格结尾的任何字符,并且设为分组3。
([0-9]{1,5}):表示匹配1-5为数字,并且设置为分组4。
有了如上的规则,就可以将a 123 b c d e 456 f 分为4组,分别为:
此时,在引用分组的时候,只需要将4 和2进行对调,则可以满足123 和456交换。
获取机器的IP地址
| Bash |
|---|
| wangli@debian:~$ ip a | sed -E -n 's/.* ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/[0-9]{1,3}.*/\1/p'
127.0.0.1
192.168.1.135
wangli@debian:~$
|
使用ip a 可以查询所有的IP信息
| Bash |
|---|
| wangli@debian:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:7c:92:af brd ff:ff:ff:ff:ff:ff
inet 192.168.1.135/24 brd 192.168.1.255 scope global dynamic noprefixroute enp0s3
valid_lft 4631sec preferred_lft 4631sec
inet6 fe80::a00:27ff:fe7c:92af/64 scope link noprefixroute
valid_lft forever preferred_lft forever
wangli@debian:~$
|
使用的正则如下:
| Bash |
|---|
| sed -E -n 's/.* ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/[0-9]{1,3}.*/\1/p'
|
使用sed -E 表示启用正则,而sed -n 表示只输出匹配的部分。
而正则表达式如下:
| Bash |
|---|
| .* ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/[0-9]{1,3}.*
|
意思为提取包含所有IP地址和子网掩码的内容,并且只提取IP部分,忽略子网掩码,其中IP 部分又规定了获取的是IPv4格式。
所以,该正则表达式含义为:
.* :匹配任何长度的字符,并且以空格结束。
([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}): 匹配IPv4地址,并且使用分组捕获。
\/[0-9]{1,3}.* :匹配斜杠/和子网掩码部分。
.*:最后的匹配后面的所有字符。
提取URL中的协议、域名和路径
| Text Only |
|---|
| wangli@debian:~$ echo 'https://1.3.3.4/hello/world.txt' | sed -E -n "s|(.*)://([^/]+)/(.*)|s1:\1 s2:\2 s3:\3|p"
s1:https s2:1.3.3.4 s3:hello/world.txt
wangli@debian:~$
|
其中正则表达式为:
| Bash |
|---|
| sed -E -n "s|(.*)://([^/]+)/(.*)|s1:\1 s2:\2 s3:\3|p"
|
其中[^/]+ 的含义如下:
[] 表示一个字符集,[^/] 则表示除了/ 以外的所有字符,后面的+ 表示上一个字符至少出现1次,这里如果使用(.*)/的话,会出现贪婪匹配。
总结
扩展正则表达式相对基础正则表达式而言,简化了写法,如{}、() 不需要在额外转义了,还新增了+ 和? 元字符。
有关正则表达式历史可以看下:正则表达式 - 维基百科,自由的百科全书 (wikipedia.org)。