<1> 正则表达式基础
<1.1>什么是正则表达式
记得我初次接触正则表达式到深入了解它,就一感觉,真长真复杂真难记,也许是我没有这方面的基础所以学起来才会显得吃力吧!到最后还是把它拿下,就跟交女朋友成功一样的喜悦,因此,我决定把它写成博客跟大家分享,不足之处希望大家能够提出来,我会稍加改进。
在老套机械化地使用抽象文字来解释“什么是正则表达式”之前,先来想想我们使用office软件中的“查找”功能。该功能似乎很简单,比如说,想要在当前文档中找到hello,只需要在查找项中输入hello就可以,可能大家没有意识到,这其实就是一种简单的“表达式”查找工具会使用某种匹配方式进行全文搜索,其工作原理也非常简单,那就是先找到h,然后看后面是不是e,再看后面是不是l,以此类推。如果全部符合,那就是匹配到了。但这里也可能会出现一个问题,这种简单的查找其实也能匹配到jelloworld(注意中间没有空格)这样的文字,不过office的查找还提供了高级功能,选中“全字匹配”就只会匹配到hello了。
在linux文本模式中,没有类似于office的图形化匹配工具,但可以使用“正则表达式”来做相同的匹配工作。还是以精确匹配hello为例,在正则表达式中就可以用\<hello\>
来表示,这里使用到了正则表达式的特殊符号。正则表达式中还有更多的更复杂的符号可以用来代表其他有意义的字符,这实际上是一种抽象的过程。
说到这里,再解释什么是正则表达式就显得简单明了了:正则表达式就是能用某种模式去匹配一类字符串的公式,它是由一串字符和元字符构成的字符串。所谓元字符,就是用以阐述字符表达式的内容,转换和描述各种操作信息的字符。
<1.2>基础的正则表达式
上一节中介绍什么是正则表达式的时候,我们第一次看到了\<hello\>
,其中的\<和\>
就是正则表达式中的两个特殊字符,也叫元字符,它代表的意思是一个单词开始或结束的位置。下面进一步介绍其他的一些元字符,以加深大家对正则表达式的理解。
1“ . ”(一个点) 符号
点符号用于匹配除换行符之外的任意一个字符。例如:r.t
可以匹配rot、rut
,但是不能匹配root
,若是使用r..t
就可以匹配到root、ruut、r t
(中间是两个空格)等。下面例子是从/etc/passwd中搜索出“包含r,紧跟着两个字符,后面再接t”的行
2 ” * “符号
" * "
符号用于匹配前一个字符0次或任意多次,比如ab*
,可以匹配a、ab、abb
等。 " * "
号经常和“ . ”
符号加在一起使用。比如“.* ”代表任意长度的不包含换行的字符。下面的例子试图找到连续的r字母紧跟着t的行。由于在/etc/passwd中没有rt、rrt这样的匹配,所以该表达式实际上只找出了包含t的行(r匹配了0次)
如果不上面的‘r*t’
换成‘r .*t’
,代表查找包含字母r,后面紧跟任意长度的字符,再跟一个字母t的行。如下图所示:
3 "\{n,m\}"符号
虽然 “ * ”可用于重复匹配前一个字符,但却不能精确地控制匹配的重复次数,使用“\{n,m\}”
符号则能更加灵活地控制字符的重复次数,典型的有以下3种形式:
\{n\}
匹配前面的字符n次。下例匹配的是包含root的行(r和t中包含两个o)。
\{n,\}
匹配前面的字符至少n次以上(含n次)。
\{n,m\}
匹配前面的字符n到m次
4 “^” 符号
这个符号位于键盘6的上面,又称尖角号。这个符号用于匹配开头的字符。比如说"^root”
匹配的是以字母root开始的行。
5 “$”符号
和上面的尖角号相对,"$"
用于尾部,比如说“abc$”
代表的是以abc结尾的行。如果是“^$”
则代表该行为空, 因为^和$间什么都没有。下例匹配的是以r开头,中间有一串任意字符,以h结尾的行。
6 “[ ]”符号
这是一对方括号,用于匹配方括号内出现的任一字符。比如说单项选择题的答案,可能是A,B,C,D中的任意一种,用正则表达式表示就是[ABCD]
。如果遇到比较大范围的匹配,比如说要匹配任意一个大写字母,就需要“-”
号做范围限定,写成[A-Z]
,要匹配所有字母则写成[A-Za-z]
。一定要注意,这里“-”
的作用不是充当一个字符。
如果是要匹配不是大写字母A,B,C,D的字符又该怎么写呢?还记得上面的“^”
号吗,如果这个字符出现在[ ]
中,则代表取反,也就是“不是”的意思。那这里的写法就是[^A-D]
,事情就变得复杂了。
这里举个例子,看如何匹配手机号。手机号是11位连续的数字,第一位一定是1,所以表示为“^1”
;第二位有可能是3(移动)或8(联通),表示为“[38]”
; 后面连续9个任意数字,表示为“[0-9]\{9\}”
; 所以整个表达式应该写为 “^1[38][0-9]\{9\}”
。
7 “\”符号
假设有个固定电话号码021-88888888,当然也可以写成021 88888888(区号和电话号码之间用空格隔开),它们的不同之处就是区号和电话号码之间使用的符号不同,一个是“-”
,一个是空格。那么,对于这个电话号码要怎么匹配呢,很容易地想到应该用“[ ]”
来匹配。但是这么写:[-]
,对吗?答案是否定的,因为“-”
放到“[ ]”
中有特别的含义。为了表示其作为一个字符的本意,就要使用“\”
符了。这个符号代表转义字符,我们可以对很多特殊的字符进行“转义”让它只代表字符本身,因此这里的写法就应该是[\ \-]
。
再举个例子,之前我们了解到“.*”
代表的是任意长度的不包含换行的重复字符。但是如果想要匹配任意长度的点号呢?这时使用转义字符就对了:“\.*”
。如果想要对“\”
符号进行转义,就可以这样写:“\ \”。
8 “\<”符号和“>”符号
这两个符号分别用于界定单词的左边界和右边界。比如说“\<hello”
用于匹配以“hello”
开头的单词;而“hello\>”
则用于匹配以“hello”
结尾的单词。还可以使用它们的组合——“\<\>”
用于精确匹配一个字符串。所以“\<hello\>”
可精确匹配单词hello,而不是helloworld等。如下所示:
[root@centos7 ~]#echo "hello" | grep '\<hello\>' |
[root@centos7 ~]#echo "hellod" | grep '\<hello\>' 这里没有输出,表示匹配不成功 |
以上讲的是8钟常见的元字符,还有一些不太常用的字符,这些字符中有不少可以使用之前讲的8种基础元字符来表示,所以我在此就仅作一些列举和简单的说明。
9 “\d” 符号
匹配一个数字,等价于[0-9],使用grep匹配这种正则表达式时可能会遇到无法匹配的问题。示例如下:
#123是一个数字,用[0-9]匹配成功
[root@centos7 ~]#echo 123 | grep [0-9] |
#但是用这种方法却匹配不成功
[root@centos7 ~]#echo 123 | grep '\d' #没有输出表示不成功,为什么呢? |
#这是因为“\d”是一种perl兼容模式的表达式,又称作PCRE,要想使用这种模式的匹配符,需要加上 -p 参数
[root@centos7 ~]#echo 123 | grep -p '\d' #这样就匹配成功了 |
10 “\b”符号
匹配单词的边界,比如“\bhello\b”可精确匹配“hello”单词。
[root@centos7 ~]#echo "hello world" | grep '\bhello\b' |
[root@centos7 ~]#echo "helloworld" | grep '\bhello\b' #这里没有匹配 |
11 “\B”符号
匹配非单词的边界,比如hello\B可以匹配“helloworld”中的“hello”
[root@centos7 ~]#echo "helloworld" | grep '\bhello\B' |
12 “\w”符号
匹配字母、数字和下划线,等价于[A-Za-z0-9].
[root@centos7 ~]#echo "a" | grep '\w' |
[root@centos7 ~]#echo "\\" | grep '\w' #这里没有匹配 |
13 “\W”符号
匹配非字母、非数字、非下划线,等价于[^A-Za-z0-9]。
[root@centos7 ~]#echo "\\" | grep '\W' #匹配 \符号 |
14 “\n”符号
匹配一个换行符
15 “\r”符号
匹配一个回车符
16 “\t”符号
匹配一个制表符
17 “\f”符号
匹配一个换页符
18 “\s”符号
匹配任何空白字符
19 “\S”符号
匹配任何非空白字符
<1.3>扩展的正则表达式
顾名思义,扩展的正则表达式一定是针对基础正则表达式的一些补充,实际上,扩展正则表达式比基础正则表达式多了几个重要的符号。不过要注意的是,在使用这些扩展符号时,需要使用egrep命令。
“?”符号
“?”符号用于匹配前一个字符0次或1次,所以“ro?t”仅能匹配rot或rt。
“+”符号
“+”符号用于匹配前一个字符1次以上,所以“ro+t”就可以匹配rot、root等。
“|”符号
“|”
符合是“或”的意思,即多种可能的罗列,彼此是一种分支关系。比如说有些地区固定电话的区号是4位数,有些地方却是3位数,这样针对不同的区号就有不同的固定电话的表示方式,如下所示:
#区号是3位的固定电话的正则表达式方式
^0[0-9]\{2\}-[0-9]\{8\}
#区号是4位的固定电话的正则表达式方式
^0[0-9]\{3\}-[0-9]\{8\}
#两种区号的固定电话号码可以如下写
^0[0-9]\{2,3\}-[0-9]\{8\}
#使用“|”符号也可以,但是显然比上面的方式麻烦
^0[0-9]\{2\}-[0-9]\{8\} | ^0[0-9]\{3\}-[0-9]\{8\}
“()”符号
”()”符号通常需要和“|”
符号联合使用,用于枚举一系列可替换的字符。比如说固定电话的区号和电话号码之间,可能用“-”
符号或者一个空格连接,用于匹配的正则表达式如下:
#使用“()”和“|”定义连接符的写法
#这样021-88888888和0511 88888888都可以匹配
^0[0-9]\{2,3\} (-|) [0-9]\{8\}
#这种写法可以换用“[ ]”符号表示
^0[0-9]\{2,3\} ( -| ) [0-9]\{8\}
虽然以上两种写法没有本质上的不同,因为“()”
和“|”
可以和“[ ]”
相互混用,但是在某些场景下,“()”
和“|”
可以做的更多,比如像hard、hold或hood等这类开头和结尾的字母都一样的单词,要匹配这些就必须使用“()”
和“|”
了。如下表示:
#使用“()”和“|”匹配hard、hold或hood
h(ar|oo|ol)d
<2> 正则表达式示例
前面我们了解了grep的一些基本用法,但是他的功能还远远不止这些。grep的英文是Global search Regular Expression and print out the line,即全面搜索正则表达式并打印出匹配行。通过前面的一些实例我们也看到,grep和正则表达式结合使用后产生的强大搜索效果。下面我将通过更多的示例来介绍正则表达式和grep结合的用法,帮助大家更深入地理解和认识正则表达式和grep。由于正则表达式中含有较多的特殊的字符,所以结合grep时,最好用单引号将正则表达式括起来,以免造成错误。
为了演示grep命令的用法,首先创建一个文RegExp.txt,文件内容如下所示:
[root@centos7 ~]#cat RegExp.txt |
接下来一起回顾grep的基本用法:
#搜索含有good单词的行
#注意:Grep默认是区分大小写的,所以这里只会打印出包含小写good的行
[root@centos7 ~]#grep 'good' RegExp.txt |
#搜索含有good单词的行,不区分大小写
[root@centos7 ~]#grep -i 'good' RegExp.txt |
#统计不含good单词的行的行数,不区分大小写
[root@centos7 ~]#grep -ivc 'good' RegExp.txt |
下面可以正式介绍正则表达式和grep结合的用法了。
1)使用“^”
匹配行首,示例如下:
#搜索以good开头的行
[root@centos7 ~]#grep '^good' RegExp.txt |
2)使用“$”
匹配行尾,示例如下:
#搜索以Good结尾的行
[root@centos7 ~]#grep 'Good$' RegExp.txt |
3)使用"^$"
组合,匹配空行,下面的命令可以计算文件中共有多少行空行:
#搜索空行的行数
[root@centos7 ~]#grep -c '^$' RegExp.txt |
4)使用方括号匹配多种可能,示例如下:
#搜索包含Good和good的行
[root@centos7 ~]#grep '[Gg]ood' RegExp.txt |
5 ) 在方括号中使用“^”
做反选,示例如下:
#搜索一个包含ood的行,但是不能是Good和good
#记住在方括号中使用尖角号表示“非”
[root@centos7 ~]#grep '[^Gg]ood' RegExp.txt |
6)使用“.”
号,示例如下:
#搜索包含一个词,该词以g开头,紧接着是两个任意字符、再接着是一个d的行。
[root@centos7 ~]#grep 'g..d' RegExp.txt |
#搜索包含一个词,该词以G或g开头,紧接着 是两个任意字符,再接着是一个d的行。
[root@centos7 ~]#grep '[Gg]..d' RegExp.txt |
#搜索这样一些行,该行包含某个单词,该词满足如下条件:
#1.第一个字符可以是G或g
#2.第二个字符可以是1或0
#3.第三个字符可以是换行符之外的任意字符
#4.第四个字符一定是d
[root@centos7 ~]#grep '[Gg][1o].d' RegExp.txt |
7)使用精确匹配,示例如下:
#搜索含有gold的行
#从搜索结果中发现golden也被匹配出来 了
[root@centos7 ~]#grep 'gold' RegExp.txt |
#正如上例所示,一般搜索时,想要搜索含有gold的行,发现golden也匹配了
#现在我们需要精确匹配到gold这个单词的行
[root@centos7 ~]#grep '\<gold\>' RegExp.txt |
#用“\b”
的效果和“\<\>”
一致
[root@centos7 ~]#grep '\bgold\b' RegExp.txt |
8)使用“*”
号,示例如下:
#搜索这样一些行,该行包含某个单词,该词满足如下条件:
#1.以g开头
#2.g后面跟零到无限个o
#3.零到无限个o后面跟d
[root@centos7 ~]#grep go*d RegExp.txt |
9)使用“.*”
号,示例如下:
#搜索这样一些行,该行包含某个单词,该单词满足如下条件:
#1.以g开头
#2.g后面一定有字符
#3.最后是d
[root@centos7 ~]#grep 'g.*d' RegExp.txt |
10)使用“-”
号,示例如下:
#文件中有一些拼写错误的单词,发现是把g1od中的o字母写成0了
[root@centos7 ~]#grep 'g1[0-9]d' RegExp.txt |
11)使用"\"
做字符转义,示例如下:
#搜索文件中包含域名www.helloworld.com的行
#从搜索的结果来看,这里的”.”号被解析成了除换行符外的任意字符
#想要把这个点只当作一个字符点来用,就需要对其使用转义符
[root@centos7 ~]#grep 'www.helloworld.com' RegExp.txt |
#这里将点做转义,则输出的结果满足预期
[root@centos7 ~]#grep 'www\.helloworld\.com' RegExp.txt |
12)使用"\{\}"
号,示例如下:
#文档中有一个单词good被拼写错了,多写了几个o
#搜索一字母g开头包含两个以上o的单词
[root@centos7 ~]#grep 'go\{2,\}' RegExp.txt |
#搜索以字母g开头,中间正好4个o的单词
[root@centos7 ~]#grep 'go\{4\}' RegExp.txt |
13)特殊的POSIX字符,示例如下:
#grep支持一类特殊的POSIX字符,列举如下:
[:alnum:] ——————文本数字字符
[:alpha :] —————— 文字字符
[:digit:]————————数字字符
[:graph:]———————非空字符(非空格、控制字符)
[:lower:]————————小写字符
[:cntrl:]————————控制字符
[:print:]————————非空字符(包括空格)
[:punct:]————————标点符号
[:space:]————————所有空白字符(新行、空格、制表符)
[:upper:]————————大写字符
[:xdigit:]————————十六进制数字(0-9,a-f,A-F)
#搜索以大写字母开头的行
[root@centos7 ~]#grep ^[[:upper:]] RegExp.txt |
#搜索以数字开头的行
[root@centos7 ~]#grep ^[[:digit:]] RegExp.txt |
14)使用扩展的正则表达式egrep,示例如下:
#搜索g和d之间至少有一个o的行
#“+”代表匹配前面的字符1次以上(含1次)
[root@centos7 ~]#egrep 'go+d' RegExp.txt |
#搜索g和d之间只有0个或1个o的行(0次或1次)
#“?”代表匹配前面的字符1次以上
[root@centos7 ~]#egrep 'go?d' RegExp.txt |
#搜索有glad或gold的行
[root@centos7 ~]#egrep 'glad|gold' RegExp.txt |
#搜索有glad或gold的行的另一种写法
[root@centos7 ~]#egrep 'g(la|ol)d' RegExp.txt |
从上面的例子可以看出,正则表达式为文件行搜索提供了强大的支持,使得搜索更为灵活,但同时也加大了使用和读写难度。要解决这个问题,只有不断地多读多用,才能较为深刻地理解正则表达式。
——本文参考王军《Linux系统命令和shell编程》