3.1 正则表达式
3.1.1 什么是正则表达式
正则表达式(也称作正规表示法)是用于描述字符排列和匹配模式的一种语法规则。它主要用于字符串的模式分割、匹配、查找及替换操作。这样枯燥的概念很难理解吧,其实正则表达式是用来匹配文件中的字符串的方法。它会先把整个文本分成一行一行的字符串,然后从每行字符串中搜索是否有符合正则表达式规则的字符串,如果有则匹配成功,如果没有则匹配失败。
比如,我们需要在兄弟连学员手册中找出有“LAmp72”班级号的学员,这个班级号的前两个字符是大写字母,最后两个字符是数字,如果不使用正则表达式,那么Shell是无法匹配所有的“LAmp72”字符串的。
还记得我们在上一章中说过正则表达式和通配符的区别(正则表达式用来在文件中匹配符合条件的字符串,通配符用来匹配符合条件的文件名)吗?其实这种区别只在Shell中适用,因为用来在文件中搜索字符串的命令,如grep、awk、sed等可以支持正则表达式,而在系统中搜索文件的命令,如ls、find、cp等不支持正则表达式,所以只能使用Shell自己的通配符来进行匹配了。
3.1.2 基础正则表达式
在正则表达式中,我们把用于匹配的特殊符号又称作元字符。在Shell中,元字符又分为基础元字符和扩展元字符。我们先来看看到底有哪些基础元字符,如表3-1所示。
表3-1 基础元字符
我们还是举一些例子来看这些基础元字符的作用吧。我们已经学习过的grep命令支持正则表达式,所以下面的练习都需要利用grep命令来演示。在使用gre命令开始练习之前,建议大家在~/.bashrc文件中建立这个别名,如下:
[root@localhost ~]# vi /root/.bashrc alias grep='grep --color=auto'
这样,grep命令所匹配的字符都会使用颜色提示,更加容易理解正则表达式所具体匹配的字符串。
1.练习文件建立
既然正则表达式是用来在文件中匹配字符串的,那么我们必须建立一个测试用的文件,才可以进行后续的实验。文件如下:
[root@localhost ~]# vi test_rule.txt Mr. Li Ming said: he was the most honest man in LampBrother. 123despise him. But since Mr. shen Chao came, he never saaaid those words. 5555nice!
because, actuaaaally, Mr. Shen Chao is the most honest man Later, Mr. Li ming soid his hot body.
这篇文档中加入了一些数字和故意写错的英文单词,是为了一会儿的实验。
2.“*”前一个字符匹配0次或任意多次
注意“*”和通配符中的“*”含义不同,它代表前一个字符重复0次或任意多次。比如,“a*”并不是匹配“a”后面的任意字符,而是可以匹配所有内容,包括空白行。我们试试:
[root@localhost ~]# grep "a*" test_rule.txt Mr. Li Ming said: he was the most honest man in LampBrother. 123despise him. But since Mr. shen Chao came, he never saaaid those words. 5555nice! because, actuaaaally, Mr. Shen Chao is the most honest man Later, Mr. Li ming soid his hot body.
为什么会这样呢?“a*”代表匹配0个a或无数个a,如果是匹配0个a,也就是每个字符都会匹配,所以会匹配所有内容,包括空白行。所以“a*”这样的正则表达式是没有任何意义的。
如果这样写正则表达式“aa*”,则代表这行字符串一定要有一个a,但是后面有没有a都可以。也就是说,会匹配至少包含一个a的行。
[root@localhost ~]# grep "aa*" test_rule.txt Mr. Li Ming said: he was the most honest man in LampBrother. But since Mr. shen Chao came, he never saaaid those words. because, actuaaaally, Mr. Shen Chao is the most honest man Later, Mr. Li ming soid his hot body
如果正则表达式是“aaa*”,则会匹配最少包含两个连续a的字符串。例如:
[root@localhost ~]# grep "aaa*" test_rule.txt he never saaaid those words. because, actuaaaally,
如果正则表达式是“aaaaa*”,则会匹配最少包含4个连续a的字符串。例如:
[root@localhost ~]# grep "aaaaa*" test_rule.txt
because, actuaaaally,
当然,如果再多写一个a,如“aaaaaa*”,就不能从这篇文档中匹配任何内容了,因为这篇文档中a最多的单词“actuaaaally”只有4个连续的a,而“aaaaaa*”会匹配最少5个连续的a。
3.“.”匹配除换行符外的任意一个字符
正则表达式“.”只能匹配一个字符,这个字符可以是任意字符。举个例子:
[root@localhost ~]# grep "s..d" test_rule.txt Mr. Li Ming said: Later, Mr. Li ming soid his hot body. #“s..d”会匹配在s和d这两个字母之间一定有两个字符的单词
如果我想匹配在s和d字母之间有任意字符的单词,那么该怎么写呢?“s*d”这个正则表达式肯定是不行的,因为它会匹配包含d字符的行,s*可以匹配任何字符。正确的写法应该是“s.*d”。例如:
[root@localhost ~]# grep "s.*d" test_rule.txt Mr. Li Ming said: he never saaaid those words. Later, Mr. Li ming soid his hot body. #最后一句话比较有意思,匹配的是“soid his hot bod”
那么,是否只写“.*”就会匹配所有的内容呢?当然是这样的,我们执行一下吧。
[root@localhost ~]# grep ".*" test_rule.txt Mr. Li Ming said: he was the most honest man in LampBrother. 123despise him. But since Mr. shen Chao came, he never saaaid those words. 5555nice! because, actuaaaally, Mr. Shen Chao is the most honest man Later, Mr. Li ming soid his hot body
4.“^”匹配行首,“$”匹配行尾
“^”代表匹配行首,比如“^M”会匹配以大写“M”开头的行。
[root@localhost ~]# grep "^M" test_rule.txt Mr. Li Ming said: Mr. Shen Chao is the most honest man
“$”代表匹配行尾,比如“n$”会匹配以小写“n”结尾的行。
[root@localhost ~]# grep "n$" test_rule.txt Mr. Shen Chao is the most honest man
注意:如果文档是在Windows中写入的,那么“n$”是不能正确执行的,因为在Windows中换行符是“^M$”,而在Linux中换行符是“$”。因为换行符不同,所以不能正确判断行结尾字符串。那怎么解决呢?也很简单,执行命令“dos2unix文件名”把文档格式转换为Linux格式即可。如果没有这个命令,则只需安装dos2unix这个RPM包即可。
而“^$”则会匹配空白行。
[root@localhost ~]# grep -n "^$" test_rule.txt 4: 8: 11:
如果不加“-n”选项,则空白行是没有任何显示的;加入了“-n”就能看到行号了。
5.“[]”匹配中括号中指定的任意一个字符,而且只匹配一个字符
“[]”会匹配中括号中指定的任意一个字符,注意只能匹配一个字符。比如[ao]要么匹配一个a字符,要么匹配一个o字符。
[root@localhost ~]# grep "s[ao]id" test_rule.txt Mr. Li Ming said: Later, Mr. Li ming soid his hot body.
而“[0-9]”会匹配任意一个数字,例如:
[root@localhost ~]# grep "[0-9]" test_rule.txt 123despise him. 5555nice! #列出包含有数字的行
而“[A-Z]”则会匹配任意一个大写字母,例如:
[root@localhost ~]# grep "[A-Z]" test_rule.txt Mr. Li Ming said: he was the most honest man in LampBrother. But since Mr. shen Chao came, Mr. Shen Chao is the most honest man Later, Mr. Li ming soid his hot body. #列出包含大写字母的行
如果正则表达式是“^[a-z]”,则代表匹配以小写字母开头的行,例如:
[root@localhost ~]# grep "^[a-z]" test_rule.txt he was the most honest man in LampBrother. he never saaaid those words. because, actuaaaally,
6.“[^]”匹配除中括号的字符以外的任意一个字符
这里需要注意,如果“^”在[]外,则代表的是行首;如果在[]内,则代表的是取反。比如“^[a-z]”会匹配以小写字母开头的行,而“^[^a-z]”则会匹配不以小写字母开头的行。
[root@localhost ~]# grep "^[^a-z]" test_rule.txt Mr. Li Ming said: 123despise him. But since Mr. shen Chao came, 5555nice! Mr. Shen Chao is the most honest man Later, Mr. Li ming soid his hot body.
而“^[^a-zA-Z]”则会匹配不以字母开头的行。
[root@localhost ~]# grep "^[^a-zA-Z]" test_rule.txt 123despise him. 5555nice!
7.“\”转义符
转义符会取消特殊符号的含义。如果想要匹配使用“.”结尾的行,那么正则表达式是“.$”是不行的,因为“.”在正则表达式中有特殊含义,代表任意一个字符。所以需要在“.”前面加入转义符,如“\.$”。
[root@localhost ~]# grep "\.$" 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\}" test_rule.txt he never saaaid those words. because, actuaaaally,
上面的两行都包含三个连续的a,所以都会匹配。但是,如果想要只显示三个连续的a,则可以这样来写:
[root@localhost ~]# grep "[su]a\{3\}[il]" test_rule.txt he never saaaid those words. #只匹配三个连续的a [root@localhost ~]# grep "[su]a\{4\}[il]" test_rule.txt because, actuaaaally, #只匹配四个连续的a
如果正则表达式是“[0-9]\{3\}”,则会匹配包含三个连续数字的字符串。
[root@localhost ~]# grep "[0-9]\{3\}" test_rule.txt 123despise him. 5555nice!
虽然“5555”有四个连续的数字,但是包含三个连续的数字,所以也是可以列出的。但是这样不能体现出来“[0-9]\{3\}”只能匹配三个连续的数字,而不能匹配四个连续的数字。那么正则表达式就应该这样来写:^[0-9]\{3\}[a-z]。
[root@localhost ~]# grep "^[0-9]\{3\}[a-z]" test_rule.txt 123despise him. #只匹配以连续三个数字开头的行 [root@localhost ~]# grep "^[0-9]\{4\}[a-z]" 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]" test_rule.txt 123despise him. 5555nice! #匹配最少以连续三个数字开头的行
而“[su]a\{3, \}[il]”会匹配在字母s或u和i或l之间最少出现三个连续的a的字符串。
[root@localhost ~]# grep "[su]a\{3, \}[il]" test_rule.txt he never saaaid those words. because, actuaaaally, #匹配在字母s或u和i或l之间最少出现三个连续的a的字符串
10.“\{n, m\}”表示其前面的字符至少出现n次,最多出现m次
“\{n, m\}”会匹配前一个字符最少出现n次、最多出现m次的字符串,比如“\{1,3\}”能够匹配字符串“zom”“zoom”和“zooom”。还是用我们的例子文件做实验:
[root@localhost ~]# grep "sa\{1,3\}i" 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 those words. #匹配在字母s和字母i之间最少有两个a、最多有三个a的字符串
3.1.3 扩展正则表达式
熟悉正则表达式的人应该很疑惑,在正则表达式中应该还可以支持一些元字符,比如“+”“? ”“|”“()”。其实Linux是支持这些元字符的,只是grep命令默认不支持而已。如果要想支持这些元字符,则必须使用egrep或grep -E命令,所以我们又把这些元字符称作扩展元字符。
如果查询grep命令的帮助,对egrep的说明就是和grep -E一样的命令,所以我们可以把这两个命令当作别名来对待。通过表3-2来看看Shell中支持的扩展元字符。
表3-2 扩展元字符