首页 文章详情

【Shell】写给小白看的正则表达式教程

码农有道公众号 | 253 2022-07-31 13:05 0 0 0
UniSMS (合一短信)

无论是前端、后端,或者是客户端,还是算法;甚至是普通的非开发的用户,我觉得正则表达式都有必要去学习了解,因为它可以极大的提升我们的工作效率,这点对于开发人员来说可能体会更加深刻,特别是在定位线上问题的时候,面对密密麻麻的日志,怎样才能找出我们需要的那几条关键的日志,正则表达式就派上用场了。

什么是正则表达式

我不会在这里直接解释”正则表达式”是什么东东,我一直认为用一些专业词汇去给一个东西下定义就是一个扯淡的事情,只会让读者更加犯迷糊。

这里,我会演示日常中的一些需求,通过这些需求,你肯定就明白了什么是”正则表达式”。

创建一个文本文件,用sublime打开,可以看到文件内容如下:

先来看第一个需求:找出文件中的java单词,我们直接在sublime的搜索框输入"java"然后点击Find,即可:

可以看到,文件中7行都出现了java这个词,而且无论"java"出现在什么位置,都被搜出来了。
现在需求变了:如果只想获取"java"出现在行尾的字符串了,换句话说,字符串不仅要包含”java”,而且还要位于行尾,同时满足这两个条件的字符串才算满足条件。


可以看到,在搜索框输入"java$",然后点击搜索,只有"java"位于行尾的第3行和第5行被标记满足条件。

好了,现在可以来解释什么是正则表达式了:”正则表达式”又称”规则表达式”,通常用来检索符合某个规则的文本。上面的例子,在正则表达式中,”$”就表示行尾,"java$"表达的规则就是位于行尾的java字符串。

正则表达式表达的规则有不少,下面按照分类给大家一一演示,演示过程中会用到grep命令,新手友情提示,这个命令也是大家必须掌握的一个命令。

鉴于正则表达式内容还是比较多的,因此还是想用两篇文章来介绍,这是第一篇。

位置规则

所谓的位置规则,其实是我自己取得一个名字,这组规则主要是用来描述你要搜索的词要出现在指定的位置才会匹配上。

正则符号含义
^
匹配行首,此符号后面的内容必须出现在行首,才能匹配
$
匹配行尾,此符号后面的内容必须出现在行尾,才能匹配
\<匹配词首,其符号后面的内容必须在单词首部,才能匹配
\>
匹配词尾,其符号前面的内容必须在单词首尾,才能匹配
^$匹配空行
\B匹配非单词边界

对于上面表格中不明白的地方,没关系,看完下面的演示,相信肯定就能全明白了。

^和$

考虑这样一个场景:需要打印出test.txt文件中"java"这个词只出现在行首的行,上面的表格中描述的很清楚,”^”表示”匹配行首”,好了,我们实操演示下:

只有第一行的"java"是位于行首,被搜索出来了。其它6行尽管都包含"java",但是都不符合位于行首这个条件。

那么如果要搜索"java"位于行尾的行,"$"就派上用场了,示例如下:


好了,”^”与”$”现在已经知道了怎么使用,那么这两个符号结合在一起用,”^java$”以及"^$"又表示什么意思了,聪明如你一定会举一反三,结合"^"和"$"的含义,”^java$”表示"java"既位于行首又位于行尾,换句话说,整行中只有一个单词java,才会被匹配到。
"^$"表示行首与行尾之间没有任务字符,也就是说匹配"空行"。注意,”空行”表示当前行不包含任何字符,包含”空格”的行不能被当做”空行”。

\<和\>

理解了"^"和"$",再来理解\<\>的含义就容易多了,这里就不过多解释了,还是给几个示例,大家对着示例以及结果慢慢琢磨,不难理解\<\>的用法。

• 搜索"java"出现在词首的行

• 搜索"java"出现在词尾的行

• 搜索"java"作为独立单词出现的行

\B

“\B”是用来匹配”非单词边界”的,作用和"\<"以及"\>"相反,什么意思了,"<java"表示匹配"java"出现在词首的行,"\Bjava"则表示匹配"java"不出现词首的行。同理,"java\B"则表示匹配"java"不出现在词尾的行。

这样说可能如果还没理解,看了示例就会秒懂,示例如下。

• 搜索"java"不出现在词首的行

  • • 搜索"java"不出现在词尾的

匹配次数规则

利用正则表达式可以匹配指定的字符出现指定的次数,这些正则表达式有以下几个:

正则符号
含义
.
表示任意单个字符。
\?
前面的字符出现0次或者1次,就会被匹配到
\+
前面的字符连续出现至少1次,就会被匹配到
*
前面的字符连续出现任意次,包括0,就会被匹配到
\{m, n\}
前面的字符至少连续出现m次,最多连续出现n次
\{m,\}前面的字符至少连续出现m次,就会被匹配到
\{,n\}前面的字符最多连续出现n次,就会被匹配到
\{m\}前面指定的字符连续出现m次,就会被匹配到

\?   \+   *

我们先来认识第一一个用于匹配次数的正则符号,它就是*,它表示之前的字符连续出现任意次数。

可以看到,3行都被匹配到了,这里稍微解释下为什么第1行也会被匹配上,"ab*"表示"a"字符后面的"b"字符可以出现任意次,当然包括0次了,第1行被匹配上相当于"b"字符被匹配到了0次。

好了,我们再看看"\+"这个正则符号,它示匹配其前面的字符至少1次,还是来看看下面的示例,可以看到第1行没有被匹配上,因为"ab\+"表示的是"a"字符后面的"b"字符至少要出现1次,第1行的字符"a"后面没有"b"字符,所以没有匹配上。

介绍完了"\+"以及"*",配合上面表格的描述,聪明的你一定也知道了"\?"的用法了,这里就不介绍了,大家可以自己猜想并动手进行实验验证。

{}系列

"\{m}\"表示匹配前面的字符m次。如果我们想要从test1.txt文件中找出哪些行包含两个连续"b",很简单,用"ab\{2\}"匹配即可。如下所示:

“\{m, n\}”表示之前的字符至少连续出现x次,至多连续出现y次,都可以被匹配到,示例如下,如图所示,"a"后面连续出现2个"b",连续出现3个"b"的行都被匹配到了。

好了,现在再来看"\{m,\}""\{,n\}"相信大家都能理解这2个正则表达式的含义了,这里也不介绍了,留给大家自己思考和实验。

.

在正则表达式中,”.”表示匹配任意单个字符,如下图所示,"a.b"表示"a"和"b"中间包含任意一个单词都会被匹配上,这里需要注意的是:空格”也算作单个字符,所以,第3行的"3aa bb"也被匹配上了。

现在问题来了,如下图所示,只想要匹配"a"后面跟着"."字符的行,用"a."去匹配是不行的,它会将"a"后面出现任何字符的行都匹配出,而第2,3行并不是我们想要的结果。

出现上面结果的原因是因为"."在正则表达式中作为一个正则符号有其特殊的含义了,而我们的需求是需要将其作为普通的字符来对待,解决办法很简单,只需要在前面加上\即可,如下所示:


常用符号

正则表达式中,用来表达特定含义的常用符号有如下几个:

正则符号含义
[[:alpha:]]匹配任意大小写字母
[[:lower:]]匹配任意小写字母
[[:upper:]]匹配任意大写字母
[[:digit:]]匹配09之间的任意单个数字
[[:alnum:]]匹配任意数字或字母
[[:space:]]匹配任意空白字符,包括"空格""tab键"等。
[[:punct:]]匹配任意标点符号
[]
匹配指定范围内的任意单个字符,如[agh]表示只要匹配上"a","g", "h"的任意一个都算匹配上。
[^  ]和[]相反,表示指定范围的任意单个字符


我们先来看看[[:alpha:]]这个符号,在介绍它之前,我们先来看看这样一个需求:需要匹配test1.txt文件中"a"后面出现三个任意字符的行,通过前面的学习,不难写出:


好了,很棒,但是现在我们改需求了:要求"a"后面出现的三个字符必须是字母,没错,这个需求就可以用[[:alpha:]]这个符号解决,[[:alpha:]]表示匹配任意大小写字母。如下所示:

可以看出,"a[[:alpha:]]\{3\}"这个正则表达式整体的含义就是:只有a字母后面跟随了3个字母的字符串才会被匹配到,如果a字母后面跟随的3个字符中包含非字母不会被匹配到。

理解了[[:alpha:]],那么其它的也很容易理解了,这里不过多再解释了,下面给出几个示例,大家对着前面表格的描述,肯定是能理解的。


我们再来介绍一个符号“[ ]”,“[ ]”表示匹配指定范围内的任意单个字符,我们还是来看个例子:

可以看到,上面的例子中,字母a后面跟随字符1或者跟随字符t、或者跟随字符4都可以被匹配到,也就是说"[1t4]"表示1或者t或者4中的任何一个字符都能被匹配到。现在,我想大家应该明白“[ ]”的含义了吧。
需要提到的一点是,如果需要匹配的字符范围比较大,并且是有规律的,其实是不需要将每个字符都写在[]中的,可以用-表示范围,比如:[1-3a-c]表示匹配字符"1","2","3","a","b","c"中的任意一个。
好了,又到了举一反三的时候了,其实前面一些常用的符号如[[:alpha:]]也有另外的表示形式:
正则表达式
等效表达式
[[:alpha:]][a-zA-Z]
[:upper:]][A-Z]
[[:lower:]][a-z]
[[:digit:]][0-9]
[[:alnum:]][a-zA-Z0-9]
[[:alpha:]]举例如下,正则表达式[[:alpha:]]和[a-zA-Z]匹配的效果是一样的

最后,我们来介绍下[]和^的结合体[^  ],它表示匹配指定范围外的任意单个字符,和[]含义相反,举个例子,[a-zA-Z]表示匹配任意单个大小写字母,那么对应的[^a-zA-Z]则表示只要不是大小写字母就匹配。这样说是不是就明白了。

分组引用

我们来了解一下正则中的”分组”与”后向引用”。首先来看看什么是分组?还是先看个案例:需要从某个文件中找出,"ha"连续出现2次的行。

可能部分同学很快会写出如下的正则表达式:

很遗憾,这种写法是错误的,结果并不是我们想要的,这是因为"ha\{2\}"中,”\{2\}”所影响的只是其前面的单个字符,也就是字母a,也就是说,"ha\{2\}"匹配的是类似"haa", "haaab"等这样的字符串。

如果想要满足我们的需求,就需要用到”分组”,将”ha”当做一个”分组”,也就是说当做一个”整体”,才可以达到我们的目的,正确的写法如下:

如上图所示,”\(ha\)”表示将ha字符串当做一个整体,所以,”\{2\}”所影响的就是前面的”ha"整个字符串。

后向引用

介绍完了分组,我们再来看看什么是后向引用,如果对分组还没理解的同学还是建议好好回过头复习下,否则后面会完全不知道在说什么。

后向引用是以分组为前提的,只有先进行分组,才能实现后向引用。为了描述清楚什么是后向引用,我们还是循序渐进看几个例子。

假设有如下一个文件test2.txt,其内容如下:

我们可以使用如下正则表达式去匹配test2.txt文件中的所有行:“h.\{3\}”表示h的后面跟随了3个任意字符,所以,”h.\{3\}”既可以匹配到haha,也可以匹配到hihi,因此文件中的三行都匹配上了。

但是,现在我只想找出”java”前后单词相同的那些行,也就是说,java之前的单词如果是haha,java之后的单词也要是haha,不能是其它,对于上面例子,第三行是不能被搜索出的。因为第三行中,java之前的单词是haha,而java之后的单词是hihi,前后不一致。
这个时候,就需要用到”后向引用”,示例如下。

可以看到,使用正则表达式"\(h.\{3\}\)java\1"达到了我们的要求,我们来分析下这个表达式的含义:

红色部分\(h.\{3\}\):表示大写字母h的后面跟随了3个任意字符,并且字母h与后面的3个字符将作为一个整体看待。

蓝色部分”\1″:表示整个正则中第1个分组中的正则所匹配到的结果,什么意思了,拿这个例子来说,整个正则表达式只有一个分组\(h.\{3\}\), 当分组与文件第一行匹配会匹配出haha,那么\1就表示haha。当分组与文件第二行匹配会匹配出hihi,那么\1就表示hihi。也就是说,”\1″引用了整个正则中第1个分组中的正则所匹配到的结果。


有"\1",那么就有"\2","\3"等等,明白了"\1"表达的含义,"\2"的含义也不难推断出,“\2″表示引用整个正则中第2个分组中的正则所匹配到的结果。“\3″表示引用整个正则中第3个分组中的正则所匹配到的结果,等等依次类推。
好了,现在的问题是在一个正则表达式中怎样区分哪个分组是第1个分组,哪个是第2个分组,特别是当分组存在嵌套时。我们还是通过一个例子来讲述吧。


上面的两个正则表达式中,都出现了两个分组,一个分组嵌套着另一个分组。红色标注的符号是一对分组符号,蓝色标注的符号是一对分组符号,红色分组嵌套这蓝色分组。

再来详细分析两个正则表达式,除了”后向引用”的数字不一样,其它都是一模一样,但是得到的结果完全不一样。

在正则表达式中,分组的顺序取决于分组符号的左侧部分的顺序,也就是说,红色标注的左侧在蓝色标注左侧的前面,所以红色标注是第1个分组蓝色标注的是第2个分组

理解了上面这一点,再来分析两个正则表达式就很简单了,第一个正则表达式"\(x\(yz\)ab\)\1"中,后向引用的是第1个分组,而第1个分组是外面的\(x\(yz\)ab\),它匹配到xyzab,\1表示后向引用第1个分组匹配的结果xyzab,那么整个正则表达式匹配的是xyzabxyzab。

第一个正则表达式"\(x\(yz\)ab\)\2"中,后向引用的是第2个分组,而第2个分组是外面的\(yz\)它匹配到yz,\2表示后向引用第2个分组匹配的结果yz,那么整个正则表达式匹配的是xyzabyz。

扩展正则表达式

前面我们介绍的都是基本正则表达式,其实在Linux中,正则表达式可以分为”基本正则表达式”和”扩展正则表达式”。

看到这里,有同学可能就懵了,好不容易将前面的内容理解的差不多了,你竟然跟我说还有扩展正则表达式,不过不要担心,只要你掌握了基本正则表达式的用法,我可以保证你掌握扩展正则表达式那是分分钟的事。

其实,扩展正则表达式和前面介绍的基本正则表达式的符号至少80%是一样的。

这里我们将两者不同的地方总结如下:

含义
基本正则表达式对应的扩展正则表达式
匹配其前面的字符0或1次\?
?
匹配其前面的字符1或多次\+
+
匹配前面的字符连续出现m次\{m}\{m}
匹配前面的字符至少连续出现m次\{m,}\{m,}
匹配前面的字符最多连续出现n次\{,n}\{,n}
匹配前面的字符连续出现至少m次,最多n次\{m,n}\{m,n}

|

可以看到,相对于基本正则表达式,对于次数匹配的很多符号,其对应的扩展正则表达式少了前面的”\”,更加简洁了,可读性也就就变强了。

下面来示范下扩展正则表达式如何用,如果希望grep命令能够支持扩展的正则表达式,需要使用”-E”选项。

我们重点来介绍下"|"的用法,基本正则表达式中没有这个符号,“|”在扩展正则表达式中,表示”或”,如果还不明白,我相信看完下面几个示例就肯定明白了。
这是一个文件test2.txt,其内容如下:

我们想要找出行首以"ha"开始的行,通过前面的学习,相信你能很快写出:

同理,如果想要找出行首以"hi"开始的行,其正则表达式如下:

好了,重头戏来了,现在我们想要找出文件中行首以"ha"开始的行,或者行首以"hi"开始的行,这就用到了"|"。“|”在扩展正则表达式中,就表示”或”的含义,如下所示:

这里再解释下,上图中的正则表达式使用了分组符号”( )”,”(ha|hi)”表示将括号内的内容看做一个整体,而括号内的内容为”ha|hi”,它表示”ha或者hi”,所以,”^(ha|hi)”就表示以ha或者hi开头的行。现在是不是明白了"|"的用法。
扩展正则表达式就介绍完了,是不是特别简单,至少我自己在学完基本正则表达式之后,我只花了几分钟就将扩展正则表达式收拾的服服帖帖。
好了,正则表达式就介绍完了,虽然不是很难,但是在一些比较复杂的场景要写出一个完全正确的正则表达式也是不容易的,后面就需要自己多练习实践了。

欢迎关注原创技术号↓↓↓
原创不易,如有帮助,辛苦点赞和在看
good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter