首页 > 编程笔记 > Linux笔记 阅读:176

Shell正则表达式用法详解(附带实例)

正则表达式(也称作正规表示法)是一种用于描述字符排列和匹配模式的语法规则。它主要用于字符串的模式分割、匹配、查找及替换操作。

这样枯燥的概念很难理解,其实,正则表达式是用来匹配文件中的字符串的方法。它会先把整个文本分成一行行字符串,然后从每行字符串中搜索是否有符合正则表达式规则的字符串,如果有就匹配成功,如果没有就匹配失败。

例如,我们需要在学员手册中找出拥有“Linux31”班级号的学员,这个班级号的首字母是大写字母,最后两个字符是数字,使用正则表达式就可以非常轻松地找出含有这个关键字的学员。因此,要牢牢记住正则表达式是一种模糊匹配字符串的方法。

还记得正则表达式和通配符的区别吗,正则表达式用来在文件中匹配符合条件的字符串,通配符用来匹配符合条件的文件名。其实这种区别只在 shell 中适用,因为用来在文件中搜索字符串的命令,如 grep、awk、sed 等可以支持正则表达式;而在系统中搜索文件的命令,如 ls、find、cp 等默认不支持正则表达式,所以只能使用 shell 自己的通配符来进行匹配了。

shell 语言是一种简化语言,主要用于帮助管理员提升管理效率,很难独立完成大型项目。基于这种目的,shell 对很多内容都做了简化,在正则表达式中也不例外。只有 shell 语言把正则表达式的元字符分为基础正则和扩展正则,其他语言的正则表达式没有这样的区分,我们分别进行学习。

请大家注意,在默认情况下正则表达式属于包含匹配,也就是说,在搜索“root”字符串时,只要含有“root”关键字的行,都会被认为符合条件。

Shell基础正则表达式

在正则表达式中,我们把用于匹配的特殊符号称作元字符。在 shell 中,元字符又分为基础元字符和扩展元字符。

先来学习使用基础元字符,如下表所示:

表:基础元字符
元字符 作用
* 前一个字符匹配 0 次或任意多次
. 匹配除换行符外的任意一个字符
^ 匹配行首。例如,^hello 会匹配以 hello 开头的行
$ 匹配行尾。例如,hello$ 会匹配以 hello 结尾的行
[] 匹配中括号中指定的任意一个字符,而且只匹配一个字符。例如,[aeiu] 匹配任意一个元音字母,[0-9] 匹配任意一位数字,[a-z][0-9] 匹配由小写字母和一位数字构成的两位字符
[^] 匹配除中括号中字符外的任意一个字符。例如,[^0-9] 匹配任意一位非数字字符,[^a-z] 匹配任意一位非小写字母
\ 转义符,用于取消特殊符号的含义
\{n\} 表示其前面的字符恰好出现 n 次。例如,[0-9]\{4\} 匹配 4 位数字,[1][3-8][0-9]\{9\} 匹配手机号码
\{n,\} 表示其前面的字符出现不少于 n 次。例如,[0-9]\{2,\} 匹配两位及以上的数字
\{n,m\} 表示其前面的字符至少出现 n 次,最多出现 m 次。例如,[a-z]\{6,8\} 匹配 6 至 8 位的小写字母

我们还是举一些例子来看这些基础元字符的作用吧。

在当前的 Rocky Linux 9.x 系统中,grep--color=auto 的别名默认永久生效,在 grep 命令结果中,--color=auto 会把找到的关键字以红色的字体显示出来,我们查看一下:
[root@localhost ~]# alias | grep grep grep
alias egrep='egrep --color=auto'
#egrep命令用于扩展正则表达式搜索
alias fgrep='fgrep --color=auto'
#fgrep命令搜索会把所有特殊符号变成普通字符搜索,也就是说在fgrep中正则表达式不起作用
alias grep='grep --color=auto'
#每当我们执行grep命令时,实际上执行的是grep --color=auto
alias egrep='xzegrep --color=auto'
#xzgrep命令,能够对压缩文件进行正则表达式搜索

1) 建立练习文件

既然正则表达式是用来在文件中匹配字符串的,那么我们必须建立一个测试用的文件,才可以进行后续的实验,文件如下:
[root@localhost ~]# vim /root/test_rule.txt
Mr. Li Ming said:
he was the most honest man.
123despise him.

But since Mr. shen Chao came,
he never said those words.
5555nice!

why not said
because,actuaaally,
Mr. Shen Chao is the good man

Later,Mr. Li ming said his hot body.
在这篇文档中加入了一些数字和故意写错的英文单词,是为了开展接下来的实验,语句不一定通顺。

2) “*”表示前一个字符匹配0次或任意多次

注意,“”和通配符中的“”含义不同,它代表前一个字符重复 0 次或任意多次。例如,“a*”并不是匹配“a”后面的任意字符,而是用于匹配 0 个 a 或无数个 a。

【实例 1】如果在“a*”的左右没有限制符号,而是单写“a*”,那么是没有意义的,这样会匹配所有内容,包括空白行,下面尝试一下:
[root@localhost ~]# grep "a*" /root/test_rule.txt
#会匹配所有内容,包括空白行
Mr. Li Ming said:
he was the most honest man.
123despise him.

But since Mr. shen Chao came,
he never said those words.
5555nice!

why not said
because,actuaaally,
Mr. Shen Chao is the good man

Later,Mr. Li ming said his hot body.
为什么会这样呢?“a*”代表匹配 0 个 a 或无数个 a,如果是匹配 0 个 a,那么每个字符都会匹配,即匹配所有内容,包括空白行。因此,“a*”这样的正则表达式是没有任何意义的。不但“a*”没有意义,而且其他任意单一符号后面添加“*”都是同样的结果。

但是,如果在“a*”的左右加限制符号,如“sa*i”,这样就有意义了,下面尝试一下:
[root@localhost ~]# grep "sa*i" /root/test_rule.txt
#匹配“sa*i”之间,没有a或有无数个a的行
Mr. Li Ming said:
But since Mr. shen Chao came,
he never saaaid those words.

why not saaid
搜索“sa*i”关键字,会在字母 s 和字母 i 之间进行匹配,没有 a 或有无数个 a 都可以匹配上。

【实例 2】如果将正则表达式写为“aa*”,就代表这行字符串一定要有一个 a,但是后面有没有 a 都可以。也就是说,会匹配最少包含一个 a 的行,案例如下:
[root@localhost ~]# grep "aa*" /root/test_rule.txt
# 匹配最少含有一个 a 的行
Mr. Li Ming said:
he was the most honest man.
But since Mr. shen Chao came,
he never saaaid those words.
why not said
because,actuaaaally,
Mr. Shen Chao is the good man
Later,Mr. Li ming said his hot body.
# 需要记住,正则表达式默认是包含匹配的

如果只是为了搜索最少含有一个 a 的行,那么也可以这样写:
[root@localhost ~]# grep "a" /root/test_rule.txt
#匹配最少含有一个a的行
Mr. Li Ming said:
he was the most honest man.
But since Mr. shen Chao came,
he never said those words.
why not said
because,actuaaally,
Mr. Shen Chao is the good man
Later,Mr. Li ming said his hot body.
这两种写法的作用相同,当然,搜索“a”比“aa*”更合理。

【实例 3】如果正则表达式是“aaa*”,那么会匹配最少包含两个连续的 a 的行,例如:
[root@localhost ~]# grep "aaa*" /root/test_rule.txt
#匹配最少有两个连续的a的行
he never said those words.
why not said
because,actuaaally,

如果正则表达式是“aaaaa*”,那么会匹配最少包含四个连续的 a 的行,例如:
[root@localhost ~]# grep "aaaaa*" /root/test_rule.txt
because actuaaaaally,
当然,如果再多写一个 a,如“aaaaaa*”,就不能从这篇文档中匹配到任何内容了,因为这篇文档中 a 最多的单词“actuaaaally”只有四个连续的 a,而“aaaaaa*”会匹配最少五个连续的 a。

3) “.”匹配除换行符外的任意一个字符

正则表达式“.”只能匹配一个字符,这个字符可以是任意字符,但不能为空或回车符,举个例子:
[root@localhost ~]# grep "s..d" /root/test_rule.txt
#匹配s和d之间有任意两个字符的行
Mr. Li Ming said:
Later,Mr. Li ming soid his hot body.
#"s..d"会匹配在s和d这两个字母之间一定有两个字符的单词

如果想要匹配在 s 和 d 这两字母之间有任意字符的单词,那么该怎么写呢?不能使用“sd”这个正则表达式,因为它会匹配包含“d”字符的行,"s"可以匹配 0 个或无数个 s。正确的写法应该是“s.*d”,例如:
[root@localhost ~]# grep "s.*d" /root/test_rule.txt
#匹配s和d之间有任意字符的行
Mr. Li Ming said:
he never saaaid those words.
why not said
Mr. Shen Chao is the good man   <-s和d之间有任意字符都可以,当然包括空格
Later,Mr. Li ming soid his hot body.

那么,是否只写“.”就会匹配所有的内容呢?当然是的,这才是标准的匹配任意内容的方法,不要用“a”来代表任意内容了,例如:
[root@localhost ~]# grep ".*" /root/test_rule.txt
#匹配任意内容,包括空白行
Mr. Li Ming said:
he was the most honest man.
123despise him.
But since Mr. shen Chao came, he never saaaid those words. 5555nice!
why not said because,actuaally, Mr. Shen Chao is the good man
Later,Mr. Li ming soid his hot body.

4) “^”表示匹配行首,“$”表示匹配行尾

“^”代表匹配行首,如“^M”会匹配以大写“M”开头的行。
[root@localhost ~]# grep "^M" /root/test_rule.txt
#匹配以大写M开头的行
Mr. Li Ming said:
Mr. Shen Chao is the most honest man

“$”代表匹配行尾,如“n”会匹配以小写“n”结尾的行。
[root@localhost ~]# grep "n$" /root/test_rule.txt
#匹配以小写n结尾的行
Mr. Shen Chao is the good man
注意,如果文档是在 Windows 中写入的,那么“n”不能正确执行,因为在 Windows 中换行符是“M$”,而在 Linux 中换行符是“$”,换行符不同,所以不能正确判断行结尾字符串。

那么解决方法是什么呢?方法也很简单,执行命令“dos2unix 文件名”把文档格式转换为 Linux 格式即可。如果没有这个命令,那么只需安装 dos2unix 这个 RPM 即可。

而“^$”则会匹配空白行:
[root@localhost ~]# grep -n "^$" /root/test_rule.txt
#匹配空白行
4:
8:
12:
14:
如果不加“-n”选项,那么空白行是没有任何显示的,加入“-n”选项后就能看行号了。

5) “[]”表示匹配中括号中指定的任意一个字符

“[]”会匹配中括号中指定的任意一个字符,注意,只能匹配一个字符,如 [ao] 要么匹配一个 a 字符,要么匹配一个 o 字符。
[root@localhost ~]# grep "s[ao]id" /root/test_rule.txt
#匹配发生在s和i之间,要么是a所在的行,要么是o所在的行
Mr. Li Ming said:
Later, Mr. Li ming soid his hot body.

而“[0-9]”会匹配任意一个数字,例如:
[root@localhost ~]# grep "[0-9]" /root/test_rule.txt
#匹配含有数字的行
123despise him.
5555nice!

而“[A-Z]”则会匹配任意一个大写字母,例如:
[root@localhost ~]# grep "[A-Z]" /root/test_rule.txt
#匹配含有大写字母的行
Mr. Li Ming said:
But since Mr. shen Chao came,
Mr. Shen Chao is the good man
Later,Mr. Li ming soid his hot body.

如果正则表达式是“^[a-z]”,就代表匹配以小写字母开头的行,例如:
[root@localhost ~]# grep "^[a-z]" /root/test_rule.txt
#匹配以小写字母开头的行
he was the most honest man.
he never saaaid those words.
why not said
because,actuaaaally,

6) “[^]”表示匹配除中括号的字符外的任意一个字符

这里需要注意,如果“^”在]外,就代表是行首;如果在 ] 内,就代表是取反。例如,“^[a-z]”会匹配以小写字母开头的行,“^[^a-z]”则会匹配不以小写字母开头的行。
[root@localhost ~]# grep "^[^a-z]" /root/test_rule.txt
#匹配不以小写字母开头的行
Mr. Li Ming said:
123despise him.
But since Mr. shen Chao came,
5555nice!
Mr. Shen Chao is the good man
Later,Mr. Li ming said his hot body.

而“^[^A-Za-z]”则会匹配不以字母开头的行:
[root@localhost ~]# grep "^[^A-Za-z]" /root/test_rule.txt
#匹配不以字母开头的行
123despise him.
5555nice!

7) “\”转义符

“\”转义符会取消特殊符号的含义。如果想要匹配使用“.”结尾的行,那么使用正则表达式是“.$”是不行的,因为“.”在正则表达式中具有特殊含义,代表任意一个字符,所以需要在“.”前面加入转义符,如“\.$”。
[root@localhost ~]# grep "\.$" /root/test_rule.txt
#匹配以“.”结尾的行
he was the most honest man in LampBrother.
123despise him.
he never saaaid those words.
Later,Mr. Li ming soid his hot body

8) “\{n\}”表示其前面的字符恰好出现n次

“\{n\}”中的 n 代表数字,这个正则表达式会匹配前一个字符恰好出现 n 次的字符串,例如,“zo\{3\}m”只能匹配“zooom”这个字符串,而“a\{3\}”就会匹配字母 a 连续出现三次的字符串。
[root@localhost ~]# grep "a\{3\}" /root/test_rule.txt
#匹配含有 aaa 的行
he never saaaid those words.
because,actuaaaally,        <-四个a也会匹配,因为其中含有三个a

上面的两行都包含三个连续的 a,因此都会匹配。但是,如果想要只显示三个连续的 a,就需要再在前后添加限制符号,例如:
[root@localhost ~]# grep "[su]a\{3\}[il]" /root/test_rule.txt
he never saaaid those words.
#只匹配三个连续的a
[root@localhost ~]# grep "[su]a\{4\}[il]" /root/test_rule.txt
because,actuaaaally,
#只匹配四个连续的a

如果正则表达式是“[0-9]{3}”,就会匹配包含三个连续数字的行:
[root@localhost ~]# grep "[0-9]\{3\}" /root/test_rule.txt
#匹配包含三个连续数字的行
123despise him.
5555nice!

虽然“5555”有四个连续的数字,但是也包含三个连续的数字,因此也可以列出。不过,这样不能体现出“[0-9]{3}”只能匹配三个连续的数字,而不能匹配四个连续的数字的效果。正则表达式就应该这样来写:^[0-9]\{3\}[^a-z](前后添加限制符号)。
[root@localhost ~]# grep "^[0-9]\{3\}[^a-z]" /root/test_rule.txt
#只匹配以连续三个数字开头的行
123despise him.

[root@localhost ~]# grep "^[0-9]\{4\}[^a-z]" /root/test_rule.txt
#只匹配以连续四个数字开头的行
5555nice!
这样就只匹配包含三个连续数字的行,包含四个连续数字的行就不能匹配了。

9) “{n,}”表示其前面的字符出现不少于n次

“{n,}”会匹配其前面的字符出现最少 n 次的字符串。例如,“zo{3,}m”这个正则表达式就会匹配在字母 z 和 m 之间最少有三个 o 的字符串。那么,“^[0-9]{3,}[a-z]”这个正则表达式就能匹配最少以连续三个数字开头的字符串。
[root@localhost ~]# grep "^[0-9]\{3,\}[a-z]" /root/test_rule.txt
#匹配最少以连续三个数字开头的字符串
123despise him.
5555nice!

而“[su]a\{3,\}[il]”会匹配在字母 s 或 u 和 i 或 l 之间最少出现三个连续的 a 的字符串。
[root@localhost ~]# grep "[su]a\{3,\}[i1]" /root/test_rule.txt
he never saaaid those words.
because,actuaaaally,
#匹配在字母s或u和i或1之间最少出现三个连续的a的字符串

10) “{n, m}”表示其前面的字符至少出现n次,最多出现m次

“{n, m}”会匹配其前一个字符最少出现 n 次、最多出现 m 次的字符串,例如,“{1, 3}”能够匹配字符串“zom”、“zoom”和“zooom”,继续使用案例文件进行验证:
[root@localhost ~]# grep "sa\{1,3\}i" /root/test_rule.txt
Mr. Li Ming said:
he never saaaid those words.
#匹配在字母s和i之间最少有一个a、最多有三个a的字符串
[root@localhost ~]# grep "sa\{2,3\}i" test_rule.txt
he never saaaid thosewords.
#匹配在字母s和i之间最少有两个a、最多有三个a的字符串

Shell扩展正则表达式

通常在正则表达式中还会支持一些元字符,如“+”、“?”、“|”、“()”。其实,Linux 是支持这些元字符的,只是 grep 命令默认不支持。如果想要支持这些元字符,就必须使用 egrep 或 grep -E 命令,因此我们又把这些元字符称作扩展元字符。

如果查询 grep 命令的帮助,就会发现其中对 egrep 命令的说明是,grep 命令和 grep -E 命令是一样的命令,我们可以把这两个命令当作别名来对待。

通过下表来看看 shell 中支持的扩展元字符:

表:shell 中支持的扩展元字符
元字符 描述
^ 匹配行首
$ 匹配行尾
. 匹配除换行符外的任意一个字符
* 匹配前面的字符 0 次或多次
+ 匹配前面的字符 1 次或多次
? 匹配前面的字符 0 次或 1 次
{n} 匹配前面的字符恰好 n 次
{n,} 匹配前面的字符至少 n 次
{n,m} 匹配前面的字符至少 n 次,最多 m 次
[abc] 匹配 a、b 或 c 中的任意一个字符
[^abc] 匹配除 a、b、c 外的任意一个字符
[a-zA-Z] 匹配任意一个字母
(abc|def) 匹配 abc 或 def

相关文章