十、 shell 正则表达式
正则表达式(Regular Expression,通常简称为 regex 或 RE)是一种表达方式,可以用它来查找匹配特定准则的文本。在许多编程语言中都有用到正则表达式,常用它来实现一些复杂的匹配。这里简单介绍一下 shell 中常用到的一些正则表达式。
一、什么是正则表达式
正则表达式是对字符串进行操作的一种逻辑公式,即用事先定义好的的一些特定字符以及这些特定字符的组合,组成一个有一定规则的字符串(Regular Expression),使用这个有一定规则的字符串来表达对字符串的一种过滤逻辑。正则表达式被广泛应用于Linux和许多其他编程语言中,而且不论在哪里,其基本原理都是一样的。
从根本上来看,正则表达式是由两个基本组成部分所建立:一般字符与特殊字符。一般字符是指没有任何特殊意义的字符;特殊字符,常称为元字符 (metacharacter),或 meta 字符,顾名思义,就是指那些有特殊意义的字符,当然在某些情况下,特殊字符也可被视为一般字符(使用转义符 \ 进行转义)。
POSIX 有两种风格的正则表达式,基本正则表达式(BRE)和扩展正则表达式(ERE)。这两种风格的正则表达式在一些字符含义上有细微的差距。以常用的 grep 指令来说,grep 指令默认支持的是 BRE,若要使用 ERE 进行匹配,可以使用 -E 选项,接下来的例子中均使用 grep 指令来演示正则表达式的使用。
二、基本正则表达式
元字符:在正则中,具有特殊意义的专用字符,如: 星号(*)、加号(+)等
前导字符:元字符前面的字符叫前导字符
1.常用 meta 字符列表
. 任意单个字符,除了换行符
* 匹配 0 个或任意多的单个字符,前置字符可以是正则表达式;ab*能匹配"a","ab"以及"abb",但是不匹配"cb"
.* 任意长度的字符,例如ab.* ,则匹配ab123 abbb abab
^ 匹配行首,如 ^abc,匹配以 abc 开头的字符串 $ 匹配行尾,如 abc$,匹配以 abc 结尾的字符串
^$ 空行 + 匹配前面正则表达式的 1 次或多次 ? 匹配前面正则表达式的 0 次或 1 次
[] 匹配指定字符组内的任一单个字符 [abc]
[^] 匹配不在指定字符组内的任一字符 [^abc]
^[] 匹配以指定字符组内的任一字符开头 ^[abc]
^[^] 匹配不以指定字符组内的任一字符开头 ^[^abc]
- 连字符,在方括号表达式中使用,表示连续字符的范围 {n,m} 区间表达式,表示匹配在它前面的字符 n 到 m次。 {n}和\{n\} 表示匹配在这之前的字符 n 次;比如[0-9]\{3\}
\{n,\}和{n,} 前导字符至少出现n次 [a-z]{4,}
\{n,m\}和{n,m} 匹配前导字符出现n次与m次之间 go{2,4}
\ 转义符 () 匹配位于方括号括起来的正则表达式群
| 匹配位于 | 符号前或后的正则表达式;^root|^ftp
\( \) 将\( 与 )\ 之间的模式保存在特殊的“保留空间”中,最多可以存储9个,可以通过后续的转义序列 \n 来匹配保留空间中的模式;保留匹配到的字符 \(hello\) \n 与 \( \) 结合起来使用,\1匹配第一个子模式、 \2匹配第二个,最多到 \9
\< 取单词的头
\> 取单词的尾
\<\> 精确匹配单词 \<hello\>
\d 匹配数字(grep -P) [0-9]
\w 匹配字母数字下划线(grep -P) [a-zA-Z0-9_]
\s 匹配空格、制表符、换页符(grep -P) [\t\r\n]
接下来,笔者将使用 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$ 将匹配一个行首字符和行尾字符相同的字符串。
示例文本
# cat 1.txt
ggle
gogle
google
gooogle
goooooogle
gooooooogle
taobao.com
taotaobaobao.com
jingdong.com
dingdingdongdong.com
10.1.1.1
Adfjd8789JHfdsdf/
a87fdjfkdLKJK
7kdjfd989KJK;
bSKJjkksdjf878.
cidufKJHJ6576,
hello world
helloworld yourself
举例说明:
需求:将10.1.1.1替换成10.1.1.254
1)vim编辑器支持正则表达式
# vim 1.txt
:%s#\(10.1.1\).1#\1.254#g
:%s/\(10.1.1\).1/\1.254/g
2)sed支持正则表达式【后面学】
# sed -n 's#\(10.1.1\).1#\1.254#p' 1.txt
10.1.1.254
说明:
找出含有10.1.1的行,同时保留10.1.1并标记为标签1,之后可以使用\1来引用它。
最多可以定义9个标签,从左边开始编号,最左边的是第一个。
需求:将helloworld yourself 换成hellolilei myself
# vim 1.txt
:%s#\(hello\)world your\(self\)#\1lilei my\2#g
# sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt
hellolilei myself
# sed -n 's/helloworld yourself/hellolilei myself/p' 1.txt
hellolilei myself
# sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt
hellolilei myself
Perl内置正则:
\d 匹配数字 [0-9]
\w 匹配字母数字下划线[a-zA-Z0-9_]
\s 匹配空格、制表符、换页符[\t\r\n]
# grep -P '\d' 1.txt
# grep -P '\w' 2.txt
# grep -P '\s' 3.txt
将192.168.0.254 换成 192.168.1.254
vim 1.txt
:%s#\(192\.168\)\.0\.\(254\)#\1\.100\.\2 //底行模式下匹配
将10.1.1.1替换成10.1.1.254
:%s#\(10.1.1\).1#\1.254#g
:%s/\(10.1.1\).1/\1.254/g
# sed -n 's#\(192\.168\)\.0\.254#\1\.1\.254#p'
找出含有192.168的行,同时保留192.168并标记为标签1,之后可以使用\1来引用它。最多可以定义9个标签,从左边开始编号,最左边的是第一个。
[root@server shell05]# sed -n 's#10.1.1.1#10.1.1.254#p' 1.txt
10.1.1.254
[root@server shell05]# sed -n 's#\(10.1.1\).1#\1.254#p' 1.txt
10.1.1.254
将helloworld yourself 换成hellolilei myself
vim 1.txt
:%s#\(hello\)world your\(self\)#\1lilei my\2#g
# sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt
hellolilei myself
[root@server shell05]# sed -n 's/helloworld yourself/hellolilei myself/p' 1.txt
hellolilei myself
[root@server shell05]# sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt
hellolilei myself
[0-9] [a-z] [A-Z] [a-zA-Z] [a-Z]
=================================================================================
Perl内置正则:
\d 匹配数字 [0-9]
\w 匹配字母数字下划线[a-zA-Z0-9_]
\s 匹配空格、制表符、换页符[\t\r\n]
#grep -P '\d' test.txt
#grep -P '\w' test.txt
#grep -P '\s' test.txt
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
该正则表达式匹配所有的大写字母。
举例说明:
示例文本
# cat 1.txt
ggle
gogle
google
gooogle
goooooogle
gooooooogle
taobao.com
taotaobaobao.com
jingdong.com
dingdingdongdong.com
10.1.1.1
Adfjd8789JHfdsdf/
a87fdjfkdLKJK
7kdjfd989KJK;
bSKJjkksdjf878.
cidufKJHJ6576,
hello world
helloworld yourself
例子2:
[root@server shell05]# grep -E '^[[:digit:]]+' 1.txt [root@server shell05]# grep -E '^[^[:digit:]]+' 1.txt [root@server shell05]# grep -E '[[:lower:]]{4,}' 1.txt
扩展类正则常用元字符
扩展元字符 功能 备注 + 匹配一个或多个前导字符 bo+ 匹配boo、 bo ? 匹配零个或一个前导字符 bo? 匹配b、 bo | 或 匹配a或b () 组字符(看成整体) (my|your)self:表示匹配myself或匹配yourself {n} 前导字符重复n次 {n,} 前导字符重复至少n次 {n,m} 前导字符重复n到m次
举例说明:
# grep "root|ftp|adm" /etc/passwd # egrep "root|ftp|adm" /etc/passwd # grep -E "root|ftp|adm" /etc/passwd # grep -E 'o+gle' test.txt # grep -E 'o?gle' test.txt # egrep 'go{2,}' 1.txt # egrep '(my|your)self' 1.txt 使用正则过滤出文件中的IP地址: # grep '[0-9]\{2\}\.[0-9]\{1\}\.[0-9]\{1\}\.[0-9]\{1\}' 1.txt 10.1.1.1 # grep '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt # grep -E '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt 10.1.1.1 # grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' 1.txt 10.1.1.1 # grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt 10.1.1.1
四、正则练习作业
1. 文件准备
# vim test.txt Aieur45869Root0000 9h847RkjfkIIIhello rootHllow88000dfjj 8ikuioerhfhupliooking hello world 192.168.0.254 welcome to uplooking. abcderfkdjfkdtest rlllA899kdfkdfj iiiA848890ldkfjdkfj abc 12345678908374 123456@qq.com 123456@163.com abcdefg@itcast.com23ed
2. 具体要求
1、查找不以大写字母开头的行(三种写法)。 grep '^[^A-Z]' 2.txt grep -v '^[A-Z]' 2.txt grep '^[^[:upper:]]' 2.txt 2、查找有数字的行(两种写法) grep '[0-9]' 2.txt grep -P '\d' 2.txt 3、查找一个数字和一个字母连起来的 grep -E '[0-9][a-zA-Z]|[a-zA-Z][0-9]' 2.txt 4、查找不以r开头的行 grep -v '^r' 2.txt grep '^[^r]' 2.txt 5、查找以数字开头的 grep '^[0-9]' 2.txt 6、查找以大写字母开头的 grep '^[A-Z]' 2.txt 7、查找以小写字母开头的 grep '^[a-z]' 2.txt 8、查找以点结束的 grep '\.$' 2.txt 9、去掉空行 grep -v '^$' 2.txt 10、查找完全匹配abc的行 grep '\<abc\>' 2.txt 11、查找A后有三个数字的行 grep -E 'A[0-9]{3}' 2.txt grep 'A[0-9]\{3\}' 2.txt 12、统计root在/etc/passwd里出现了几次 grep -o 'root' 1.txt |wc -l 13、用正则表达式找出自己的IP地址、广播地址、子网掩码 ifconfig eth0|grep Bcast|grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' ifconfig eth0|grep Bcast| grep -E -o '([0-9]{1,3}.){3}[0-9]{1,3}' ifconfig eth0|grep Bcast| grep -P -o '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}' ifconfig eth0|grep Bcast| grep -P -o '(\d{1,3}.){3}\d{1,3}' ifconfig eth0|grep Bcast| grep -P -o '(\d+.){3}\d+' # egrep --color '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' /etc/sysconfig/network-scripts/ifcfg-eth0 IPADDR=10.1.1.1 NETMASK=255.255.255.0 GATEWAY=10.1.1.254 # egrep --color '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' /etc/sysconfig/network-scripts/ifcfg-eth0 IPADDR=10.1.1.1 NETMASK=255.255.255.0 GATEWAY=10.1.1.254 14、找出文件中的ip地址并且打印替换成172.16.2.254 grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt |sed -n 's/192.168.0.\(254\)/172.16.2.\1/p' 15、找出文件中的ip地址 grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt 16、找出全部是数字的行 grep -E '^[0-9]+$' test 17、找出邮箱地址 grep -E '^[0-9]+@[a-z0-9]+\.[a-z]+$' grep --help: 匹配模式选择: Regexp selection and interpretation: -E, --extended-regexp 扩展正则 -G, --basic-regexp 基本正则 -P, --perl-regexp 调用perl的正则 -e, --regexp=PATTERN use PATTERN for matching -f, --file=FILE obtain PATTERN from FILE -i, --ignore-case 忽略大小写 -w, --word-regexp 匹配整个单词
参考:https://www.cnblogs.com/tongye/p/9727147.html