首页 > Linux操作系统 > Linux操作系统 > Linux正则表达式详解
正则表达式(Regular Expression)的概念和发展可以追溯到20世纪50年代和60年代的计算机科学和形式语言理论的研究。正则表达式的发展涉及到多位科学家和计算机科学家的贡献。其中,美国计算机科学家和数学家Stephen Cole Kleene(斯蒂芬·科尔·克莱尼)被认为是正则表达式的先驱。他在20世纪50年代的研究中,提出了正则语言(Regular Language)的概念和形式化定义,为正则表达式的发展奠定了理论基础。他的研究成果在形式语言理论和计算机科学中有重要的影响。
在正则表达式的实际应用和发展中,美国计算机科学家和Unix操作系统的创始人之一Ken Thompson(肯·汤普森)也发挥了重要的作用。在20世纪70年代,他在贝尔实验室开发的Unix操作系统中引入了正则表达式的概念和实现,将正则表达式作为一种强大的文本处理工具引入了计算机科学和软件开发的领域。
简单来说,通过正则表达式,可以模糊匹配到满足条件的项,继而实现对该项的多维度操作。一般而言,Linux系统上经常使用到正则表达式的命令有vi、sed、grep、awk。
正则表达式在Linux实际使用时候,格式很简单,一般表示如下:
/pattern/(sed/awk)或者 'pattern'(grep)
其中,pattern(模式)代表需要进行匹配的内容,在Linux中,pattern分为普通正则以及扩展正则两种模式,在扩展正则下,新增/修改了部分pattern,常用的pattern请见下一章节。
*注:如果需要使用扩展正则,在sed/grep中,请使用-E参数。但在awk中,默认就是扩展正则形式。
假如有一个需求,想要把a.txt中包括关键字test的行取出来,通常情况下有如下三种方式:
sed -n '/test/p' a.txt
grep 'test' a.txt
awk '/test/' a.txt
这里,完整的单词test就属于一种pattern(模式),表示匹配test这个单词,当然,除了用完整的单词作为pattern,我们还可以使用如下正则表达式来作为pattern进行匹配:
字符
普通字符,包括“test”或者“hello”等
字符类,比如[],所以[abc%8!]表示a或者b或者c或者%或者8或者!中任意一个
范围类,比如-,[a-d]表示一个小写字母a/b/c/d。同理[0-9]表示任意一个数字;[a-z0-9A-Z]表示任意一个字母或数字
反向字符类,用^表示的反向字符类匹配不在字符类中的任意字符。例如正则表达式[^0-9]匹配一个任意非数字字符。
通用类,用.表示除\n\s之外的任意一个字符
重复
*,表示前面的字符0次或者多次
\+,表示前面的字符1次或多次
*注:如果在awk中使用该功能,请参考扩展正则
\?,表示前面的字符0次或者1次
*注:如果在awk中使用该功能,请参考扩展正则
\{m\},表示前面的字符重复m次
*注:如果在awk中使用该功能,请参考扩展正则,下同
\{m,\},表示前面的字符重复m以及m次以上
\{,n\},表示前面的字符重复m次以内(包括0次)
\{m,n\},表示前面的字符重复m到n次(闭区间,并且n>=m)
特殊用法
^,表示行开头,这里需要注意与[]中的^区别,比如^[^abc]表示以非a/b/c字符开头
$,表示行结尾
\|,表示或者
*注:如果在awk中使用该功能,请参考扩展正则
\b,表示边界,比如说\bthis可以匹配thisabc或者%this或者this,但不能匹配tttthis
*注:该方法不能在awk中使用
\<,表示左边界
\>,表示右边界
\,用于转义特殊字符
\(\),表示整体(可以改变操作符的优先级)或者将匹配项捕获到\1\2中
*注:如果在awk中使用该功能,请参考扩展正则。另,awk取出匹配内容不能使用\1或者\2,而是通过match捕获
\1,表示使用\(...\)所取出来的第一个内容,比如说模式\([a-z]\)\1,表示连续的两个相同小写字母
*注:该方法不能在awk中使用
重复
{m},表示前面的字符重复m次
{m,},表示前面的字符重复m以及m次以上
{,n},表示前面的字符重复m次以内(包括0次)
{m,n},表示前面的字符重复m到n次(闭区间,并且n>=m)
?,表示前面的字符重复0或者1次,等价于{,1}或者{0,1};同理,*等价于{0,}
+,表示前面的字符重复1次或者多次,等价于{1,}
特殊用法
|,表示或者
(), 表示整体(可以改变操作符的优先级)或者将匹配项捕获到\1\2中
可以发现,扩展正则是对普通正则的一种简化,为了避免不必要的歧义以及麻烦,建议用户在使用正则时候,直接通过-E参数来在sed/grep中使用扩展正则。
我们假设有b.txt文件如下:
[root@n110 ~]# cat b.txt
xxx1
xx1
x1
x
x+
x*
*.*
.
.+
thisis
thatis
thisab
thatab
thab
tatle
ttle
hab
%hab+
xyz!abc123def
456xyzabc123def
打印x出现连续3次的行
grep 'x\{3\}' b.txt #或者 grep -E 'x{3}' b.txt
sed -n '/x\{3\}/p' b.txt #或者 sed -n -E '/x{3}/p' b.txt
awk '/x{3}/ {print}' b.txt

打印x+英文小写字母+y的行
grep 'x[a-z]y' b.txt sed -n '/x[a-z]y/p' b.txt awk '/x[a-z]y' b.txt
打印x+x(出现0次或者多次)+y的行
grep 'xx*y' b.txt sed -n '/xx*y/p' b.txt awk '/xx*y/' b.txt
打印x+(x或者y)+x的行
grep 'x[xy]x' b.txt sed -n '/x[xy]x/p' b.txt awk '/x[xy]x/' b.txt
打印以xx开头的行
grep '^xx' b.txt sed -n '/^xx/p' b.txt awk '/^xx/' b.txt

打印不是以小写字母x或者t开头的行
grep '^[^xt]' b.txt sed -n '/^[^xt]/p' b.txt awk '/^[^xt]/' b.txt

打印包括tha或者tat的行
grep 'tha\|tat' b.txt #或者 grep -E 'tha|tat' b.txt sed -n '/tha\|tat/p' b.txt #或者 sed -n -E '/tha|tat/p' b.txt awk '/tha|tat/' b.txt

打印t+ha或者a+t的行
grep -E 't(ha|a)t' b.txt sed -n -E '/t(ha|a)t/p' b.txt awk '/t(ha|a)t/' b.txt
*注:此处是()的运用之一,将所括部分当成整体来匹配,当然,括号还可以用以结合\1以及\2,匹配更多的内容
打印连续两个相同小写字母的行
grep -E '([a-z])\1' b.txt sed -n -E '/([a-z])\1/p' b.txt
*注:括号部分将一个小写字母选中,后续的\1代表该选中内容,如果能够连续匹配,则表示两个相同小写字母

下面我们来看几个更加复杂的例子。
如果某行匹配连续小写字母+连续数字,那么打印匹配的字母部分
grep -o -E '[a-z]+[0-9]+' b.txt | grep -o -E '[a-z]+' #这里-o参数表示仅输出匹配部分
sed -n -E 's/(.*[^a-z]|^)([a-z]+)[0-9]+.*/\2/p' b.txt #这里将整行数据全部替换成取到的\2,因为匹配[a-z]+[0-9]+的前一个字符要么是开头,要么是.*加上一个非[a-z]的字符
awk 'match($0,/([a-z]+)[0-9]+/,arr) {print arr[1]}' b.txt #这里使用awk中的match语法,将匹配的内容存放在数组arr中,注意arr[0]代表正则中所能匹配的所有内容,arr[1]代表第一个括号所匹配的内容,arr[2]代表第二个括号匹配内容。

*注:如果单行中出现多个[a-z]+[0-9]+,比如说xyz! abc123def456这种
上述grep命令会将两次 匹配的[a-z]+[0-9]+中[a-z]+内容打印出来,即abc与def都会打印在不同行。如果需要打印第一个匹配的内容中[a-z]+部分,请使用-m参数控制输出行
上述sed命令会打印第二次匹配的[a-z]+[0-9]+中[a-z]+内容,也就是字符串def(.*的贪婪匹配)
上述awk命令仍然只会打印第一次匹配到的内容

打印is是右边界的行
grep 'is\b' b.txt 或者 grep 'is\>' b.txt sed -n 's/is\b/p' b.txt 或者 sed -n 's/is\>/p' b.txt awk '/is\>' b.txt

打印仅匹配单词hab的行(注:连续字母thab不能被打印)
grep '\bhab\b' b.txt grep '\<hab\>' b.txt sed -n '/\bhab\b/p' b.txt awk '/\<hab\>/' b.txt grep -w 'hab' b.txt # 直接使用-w参数(word)

打印x+a或者b出现0次或多次+y
grep -E 'x[ab]{0,}y' b.txt
grep 'x[ab]*y' b.txt
sed -n '/x[ab]*y/p' b.txt
sed -n -E '/x[ab]{0,}y/p' b.txt
将第一次出现的连续字母+连续数字的位置对调,并打印匹配行中对调后内容
sed -n -E 's/([a-z]+)([0-9]+)/\2\1/p' b.txt
awk 'match($0,/([a-z]+)([0-9]+)/,arr) {print arr[2] arr[1]}' b.txt

以上内容基本涵盖了常用的正则匹配,但在实际使用当中,还是需要将正则内容组合起来,实现更复杂内容的匹配与选取。我们再看下面几个例子:
[root@n110 ~]# cat d.txt
this is cat!
this is THE cat!
this is THE THE cat!
THE THE THE cat!
this is THE THE THE cat!
this is THE THAT THE cat!
this is THE THE THE THE cat!
this is THE THE THE THE THEcat!
this isTHE THE THE THE THEcat!
this is A A A cat!
123
thisis123
去掉d.txt中重复的独立单词THE
第一反应通常是将连续的THE替换成第一个,我们尝试命令如下:
sed -E 's/(THE )+/\1/g' d.txt

上述红框内容出现了异常,按理说原有该行内容为“ this isTHE THE THE THE THEcat!”,去除完独立THE之后,应该剩余“ this isTHE THE THEcat!”,出现红框中原因是isTHE也被当成一个独立的THE,所以为了避免该错误,我们改进命令如下:
sed -E 's/\b(THE )+/\1/g' d.txt

此时显示正确!
另外,该命令还可以使用如下命令代替:
sed -E 's/\b(THE )\1+/\1/g' d.txt #先出现一个独立的THE,再用\1代表该内容,使用+表示该内容出现1次或者多次,最后替换成\1,也就是不管几次匹配,最后都替换成一次匹配
去掉d.txt中所有重复的独立单词(依据d.txt内容来看,不仅需要去除连续单词THE,还要去除连续单词A)
第一个想到的方法,就是将连续的单词替换成\1:
sed -E 's/\<([a-zA-Z]+ )+/\1/g' d.txt

不过我们发现结果完全不对,原因在于([a-zA-Z]+ )上,因为该部分内容匹配任何的英文字母,而不是任何重复的英文字母,比如说“this is character A”,上述正则既匹配“this ”,也匹配“is ”,同时也匹配“character ”,但不匹配最后一个单词“A”。而且,如果([a-zA-Z]+ )匹配多处内容,那\1会以最后一处内容为准:

*注:关于sed 's///1'中的1表示一行中第一个匹配的内容。
回到最初的问题: 去掉d.txt中所有重复的独立单词,此时我们就需要改进命令如下:
sed -E 's/\<([a-zA-Z]+ )\1+/\1/g' d.txt #先取出连续的字母,再使用\1+表示同样的内容重复多次,最后替换成该内容

Linux的正则是用来处理文件最强大的手段之一,可以用在grep/sed/awk以及vi中,结合这些命令本身来进行查找、打印以及替换等功能。有了正则,我们可以用来匹配任何文本内容,包括字母、数字、开头/结尾、任意指定的字符、边界以及重复的次数等等。一个典型的正则表达式通常由
/开头(字符A|字符B)重复次数+特殊用法/组成,在Linux的正则中,有多种方式来表示“字符”,同样也有多种方式来表示“重复次数”。另外,需要注意的是普通正则与扩展正则的异同,为了简便起见,我们建议直接使用扩展正则来代替普通正则以达到易于书写并且方便阅读的目的。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69985104/viewspace-2999485/ ,如需转载,请注明出处,否则将追究法律责任。