2019-06-27-基础正则的五朵金花

在前一篇《正则表达式基础入门》中,我们总结了跟“位置匹配”的相关的正则,它其实也是基础正则五朵金花之一,我们只是在入门介绍中单独拿出来进行科普。这篇文章我们就来掰扯掰扯基础正则的其它四朵金花。

基础正则的连续次数匹配

连续次数匹配是什么意思?空口白话不容易说清楚,我们还是举个例子来说明。为了说明连续次数匹配的,我们首先得重新创建一个测试文件,如下:

现在,我们想查找测试文件中哪些行包含的连续的两个字母a。聪明如你,肯定想到了如下的查找办法:

如上图,我们通过匹配“aa”就能将包含连续两个a的行搜索出来(注意:上面的条件是指至少包含两个连续的a)。那么,我们现在需要找出连续10个a的行呢?简单。。。查找“aaaaaaaaaa”不就行了。但是,如果要找包含连续100个a的行呢?上面的笨办法显然不科学,这时候就要用了基础正则中连续次数匹配条件—”{n}“了,里面n代表具体的数字,也就是连续多少个的意思。上面的示例,我们使用基础正则中的连续匹配条件可以改写成下面的格式:

如上图,结果符合我们的预期。“{2}”表示连续出现2次的意思,a\{2\}就表示连续出现两次a的意思。现在,你肯定能举一反三。比如:如果我们要匹配连续100个a,可以写成如下匹配条件“a\{100\}”,也就是我们只需要修改其中的数字n即可。

上面的用法刚才也提到了,它实际的意思的是:“包含至少2个连续a的行”。如果,我们只想找到“只包含连续两个a的行”时怎么办?这时候,就要用到我们上一篇提到的“位置规则”匹配条件。我们可以配合使用单词界定符”\<,\>”或者”\b”,具体命令如下:

如上图,我们单词界定符“\b”(前后的\b表示单词界定符,中间的字母b表示要查找的内容,后面花括号中的数字2表示要匹配的次数),将测试文件test01中的“只包含连续两个b的行找了出来”。那么,我们现在再继续延伸一下,首先来验证下d\{2,3\}的匹配条件表示什么意思,如下:

上图中的匹配条件将“包含至少2个连续的d,至多3个连续的d的行”查找了出来。也就是说“\{x,y\}”这类匹配条件,表示的是“至少连续出现x次,至多连续出现y次”的意思。举一反三,“\{x

,\}”表示“至少连续出现x次,至多不限次数”的意思,”\{,y\}”表示“至少出现0次,至多连续出现y次”的意思。

我们现在用中学数学的方法小结一下:\{x,y\}表示“至少连续出现x次,至多连续出现y次”。如果y=0,表示“至少连续出现x次,至多次数不限”,且“\{x\}”和“\{x,\}”的意思是一样的。如果x=0,表示“至多出现y次,至少出现0次”的意思。

现在,我们再来认识另一个匹配次数的正则符号“*”。其实,在通配符中也有符号“*”,表示匹配任意长度的任意字符。但是,在正则表达式中,符号“*”表示之前的字符连续出现任意次的意思。注意:千万不要和通配符中的“*”混淆了。具体使用方法如下:

如上图,表示“搜索字母a前面出现任意个d的行”,任意个d当然也包含0个d。那么,如何在正则表达式中查找任意长度的任意字符呢?我们可以使用“.*”来实现,如下:

上图中,表示匹配字母a后面接任意多个任意字符的行。其实,在正则表达式中“.”符号表示匹配任意单个字符。而在通配符中“?”表示单个字符,这一定同样要注意不要混淆。如下:

上图中,匹配条件“aa.”表示aa后面跟任意单个字符的行都会被匹配到,”aa..”表示aa后面跟任意两个字符的行都会被匹配到(空格也算字符)。理解了上述规则,回过头来我们再看“.”就非常容易理解了,**”.\“就表示任意单个字符连续出现多次的意思。**

接下来,我们再认识两个新的匹配条件:“\?”“\+”“\?”表示匹配其前面的字符出现0次或1次,也就说要么没有,要么最多出现1次。“\+”表示匹配前面的字符出现至少1次,也就是说前面的字符必须至少出现1次。我们再通过示例验证下:

如上图,“a\?”表示a这个字符出现0次或1次的行,不仅匹配到了空行,也将包含连续多个a的也匹配到了。我们再看下面的例子:

上图中,将a字符至少出现1次的行都匹配输出,那么空行也就自然不输出了。

到此,基础正则连续次数匹配规则就全部介绍完了,国际惯例,我们还是总结一下,便于后续用到了查询:

  • *符号:表示前面的字符连续出现任意次,包括0次。
  • .符号:表示单个任意字符。
  • .*组合:表示任意长度的任意字符。
  • \?组合:表示匹配前面的的字符0次或1次。
  • \+组合:表示匹配其前面的字符至少1次,或连续多次,连续次数上不封顶。
  • \{n\}符号:表示前面的字符连续出现n次,将会被匹配到
  • \{x,y\}组合:表示前面的字符至少连续出现x次,最多连续出现y次,都能被匹配到,换句话说就是之前的字符连续出现的次数在x和y之间,即可被匹配。
  • \{,n\}组合:表示之前的字符连续出现至多n次,最少0次,即可被匹配。
  • \{n,\}组合:表示之前的字符连续出现至少n次,才会被匹配到。

基础正则中的常用符号

之前介绍的使用正则表达式去“匹配连续次数”中,我们使用过特殊符号“.”和”*”,分别用于匹配单个任意字符和连续出现任意次数。如果我们需要更精确的匹配呢?比如我们想从测试文本中找出a字母后接任意3个的字母的字串所在行,我们可以使用如下规则:

如上图,“[[:alpha:]]”就是我们现在需要认识的新符号。在正则表达式中,“[[:alpha:]]”表示“任意字母(不区分大小写)”。“[[:alpha:]]”这个符号看上去有些复杂,吐啊。。。吐啊。。。习惯就好了。其实,这个符号可以拆分成两部分去理解,所以也没那么可怕。“[[:alpha:]]”的第一部分是最外层的[ ],表示指定范围内的任意单个字符,第二部分是最内层的[:alpha:],表示不区分大小写的任意字母。所以,当两部分合起来就是表示任意单个字母(不区分大小写)。

上面的[[:alpha:]]表示不区分大小写的任意字母,如果我们现在需要区分大小写呢?比如,上面的示例我们再细化一下,需要匹配a后面跟随的3个字母必须是小写字母,可以使用如下规则:

上图中,[[:lower:]]就是我们现在认识的第二个符号,它在正则表达式中表示任意单个小写字母。同样的,[[:upper:]]就表示任意单个大写字母。

聪明如你,一定发现了一些规律,那就是替换“[[: :]]”中的单词,就可表示不同的含义。在基础正则中,主要包含如下特殊符号:

  • [[:alpha:]]:表示任意1个不区分大小写的字母。
  • [[:lower:]]:表示任意1个小写字母。
  • [[:upper:]]:表示任意1个大写字母。
  • [[:digit:]]:表示0-9之间任意1个数字,包括0和9。
  • [[:alnum:]]:表示任意1个数字或字母。
  • [[:space:]]:表示任意1个空白字符,包括“空格”、“tab键”等。
  • [[:punct:]]:表示任意一个标点符号。

除了上面的特殊符号表示法外,还有其他办法实现 上述的匹配效果。还记得我们在正则表达式基础入门一文中举得一个[a-z]*的例子不?示例中,[a-z]其实就表示任意一个小写字母,效果与[[:lower:]]一样。同理,[A-Z]就与[[:upper:]]是等价的,表示任意一个大写字母;[0-9]与[[:digit:]]是等价的,表示任意一个0-9的数字。如下所示:

那么,有了上面的基础,“[a-z][A-Z][0-9]”就表示任意一个不区分大小写的字母或0-9的数字,等价于[[:alnum:]]。

上述两种表示的特殊符号使用没有强制性,看个人喜好而定。这里还需要强调下[ ]的作用,它表示匹配指定范围内的任意单个字符。还是先看下面的示例:

如上图,字母a后面接一个方括号[],里面有bcd三个字母。既然方括号[ ]表示匹配任意范围内的单字符,那么上面的匹配条件就是“匹配字母a后面紧跟b或c或d三个字母一次的行”。我们活学活用,[Bd#3]表示什么意思呢?它就表示字符是大写B、或者是小写d、或者是特殊符号#、再或者是数字3各一次的场景,都可以被匹配到。如下:

上图中,表示匹配包含om或oe或oo或ob字符串的行。

现在,我们理解了方括号[ ]的含义,那么我们再来认识一下它的性格迥异的亲兄弟[^ ],它表示匹配指定范围外的任意一个字符,与方括号[ ]的含义正好相反。还是看下面示例:

如上图,与前面示例的效果正好相反,将测试文件中不包含om,或不包含oe,或不包含oo,或不包含ob字符的行匹配输出。同理,[^0-9]就表示匹配单个非数字字符,[^a-z]就表示匹配单个非小写字母等等,这里就不再举例了,很好理解。这里有个疑问,比如前面说了[0-9]与[[:digit:]]是等价的,那么[^0-9]与[^[:digit:]]是否等价?试试就知道了,如下:

针对这些常用符号,我们总结出一张表如下:

符号 等价于 含义
[a-z] [[:lower:]] 任意一个小写字母
[^a-z] [^[:lower:]] 任意一个非小写字母
[A-Z] [[:upper:]] 任意一个大写字母
[^A-Z] [^[:upper:]] 任意一个非大写字母
[0-9] [[:digit:]] 任意一个0-9的数字
[^0-9] [^[:digit:]] 任意一个非0-9的数字
[a-zA-Z] [[:alpha:]] 任意一个不区分大小写的字母
[^a-zA-Z] [^[:alpha:]] 任意一个非字母的字符
[a-zA-Z0-9] [[:alnum:]] 任意一个数字或字母
[^a-zA-Z0-9] [^[:alnum:]] 任意一个非数字或字母的字符,比如符号

其实,不仅[0-9]、[[:digit:]]可以匹配数字,还有一种简写的格式符号也能表示数字,比如”\d”就是表示十进制数字0-9。但是,并不是所有的正则表达式解释器都能够识别这些简写格式。因此,建议大家还是采用上述非简写格式来编写规则。

基础正则中的分组与后向引用

在正则表达式中除了使用“位置匹配”、“连续次数匹配”和“常用符号”外,还可以使用分组和后向引用。那么,什么是分组?我们还是通过示例来说明,如下先创建一个测试文件test02:

我们先使用如下规则进行匹配:

如上图,后面的匹配次数2只影响前面单字母b,所以上例中kkutysllbb、kkutysllbbb都会被匹配到。如果,我们想将上面示例中匹配要求改成连续匹配两次kkutysllb怎么办?这个时候,我们就需要使用分组的匹配的规则,将kkutysllb当做一个整体group来匹配。命令规则如下:

上图中,\( \)就表示分组,它将其中的内容看做一个整体。分组还可以嵌套,什么意思呢?接着往下看:

如上图,为了说明分组嵌套,我们将上例中存在重复字母的地方做了单字母分组,实际环境时没必要这样分组。在上图中,我们将字母k和字母l做了两个子分组,且指定每个子分组各连续出现两次。同时,将kkutysllb做了一个大分组,包括上述的两个子分组,且指定整个大分组也连续出现两次。

我想我把分组的概念应该是说清楚了,接下来我们再看看什么是后向引用?之所以先讲分组,后介绍后向引用,是因为后向引用是以分组为前提的。还是以示例切入介绍,我们再创建一个测试文件test03,如下:

现在,我们通过正则表达式将上述文件中的各行进行匹配,如下:

H.\{4\}表示大写字母H的后面跟随了4个任意字符,其中”.”表示任意单个字符,前面已经说过了。通过上面的匹配规则,Hello和Hilll两个单词都会被匹配到,也就是上述测试文件三行都符合规则。我们现在有了新需求,需要找出kkutysllb单词前后两个词一致的行,通过上面的规则明显无法满足需求,这时候就需要用到后向引用了。如下:

上图中,后面那个\1就表示后向引用,它的意思是kkutysllb后面的单词必须与前面\(H.\{4\}\)完全一致才符合规则。这究竟是什么道理呢?其实,\1的意思是等价整个正则中第1个分组的正则匹配到的结果。大白话就是,上例中整个正则只有一个分组\(H.\{4\}\),当它与测试文件的第一行文本匹配时,会匹配到Hello,这时\1也为Hello。当它与测试文件第二行文本匹配时,会匹配到Hilll,这时\1也为HIlll。换句话说,\1引用整个正则中第1个分组的正则,同理,\2就引用了整个正则中第2个分组的正则,以此类推。。。如果你还是不明白,那就看下图理解吧,如果仍然不明白,那我也没招了。。。

以上就是后向引用,再次强调,使用后向引用的前提是将需要引用的部分进行分组。

在前述内容的例子中,我们使用了分组嵌套,那么在对分组嵌套后向引用时,到底哪个是分组1,哪个是分组n呢?我们来看下图:

在上图中,红色部分是一对分组,蓝色部分是另一对分组,当我们需要后向引用时,外层分组也就是红色分组是第1个分组,内层分组也就是蓝色分组是第2个分组。这是因为分组的顺序取决于分组符号的左侧部分的顺序,由于红色分组的左侧部分排在最前面,所以红色分组是第1个。

至此,分组和后向引用我们掰扯完了,我们还是保持国际惯例,对分组和后向引用做个总结,便于我们后续查询,如下:

  • \( \):表示分组,我们可以将其中的内容当做一个整体,分组是可以嵌套的。
  • \(ab\):表示将ab当做一个整体去处理。
  • \1:表示引用整个正则表达式中第1个分组中的正则匹配到的结果。
  • \2:表示引用整个正则表达式中第2个分组中的正则匹配到的结果。

基础正则中的转义符

现在,我们来认识下正则中的常用符合,就是反斜杠”\“。反斜杠有什么用?我们还是先不解释,通过示例来描述。如下,我们还是创建的一个测试文件test04,内容如下:

1
2
3
4
5
6
7
[root@c7-test01 ~]# cat test04
base
a1#$
ddd
a-!@
cccc
a..

如上,此时我们想匹配a..这行,利用我们前面了解的知识,规则可能会这样写。匹配条件如下:

上图中,虽然匹配结果包含了我们的需求,但是也多了其他三行,这让我们很不爽。主要是因为点符号”.”在基础正则中表示任意1个字符。因此,我们如果想要在匹配条件中匹配点点,就需要使用转移符号反斜杠“\”。转义符号“\”与正则中的符号结合起来就表示这个符号本身。因此,上面的需求我们可以使用如下的匹配条件:

如上图,\.表示单个点符号,同理\*就表示单个星符号。在前面提到过,在基本正则中,\?表示其前面的字符出现0次或1次,\+表示前面的字符出现至少1次。那么,如果我们相匹配问号?和加号+呢?难道我们还要再在前面加一个反斜杠\\?或\\+,答案是否定的。在Linux中,如果想要在正则表达式中匹配问号?或加号+,只需要在匹配条件中直接输入?或+即可,大家可以自己尝试。但是,在某些时候,我们需要匹配反斜杠自身,这时需要在反斜杠前面再加上反斜杠就行。

下面我们写个经常会使用的正则表达式—-匹配ifconfig输出中的各个网卡的ip地址。如下:

此时,我们需要首先对ipv4的地址做个分析,首先它是由4组三位数组成,且每组数字不大于255,前3组三位数后面都带一个点号”.”,那么我们的正则规则可以如下来写:

上图中,([0-2]{,1})([0-9]{1,2}).表示至少为1位数字,最多为3位数字,且首位数字不大于2,后面以点号“.”结尾的字段,然后将这部分作为一个分组连续匹配3次,最后这一段([0-2]{,1})([0-9]{1,2})也表示至少为1位数字,最多为3位数字,且首位数字不大于2。注意:上面的ip地址匹配并不精确,我们其实并没有限制每组数字范围在1-254。不过,没关系,当我们了解了扩展正则就能写一个更精确的ip地址匹配规则。

至此,基础的正则的五朵金花也就介绍完了。在正则入门时我们说过,Linux中正则表达式分为基础正则表达式与扩展正则表达式,在下一篇文章中我们会介绍扩展正则,其实它们的用法都是相似的,而且写法也差不多,基础正则理解并掌握了,扩展正则几乎不费力。

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!