加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

正则表达式与贪婪规则

发布时间:2020-12-14 01:28:49 所属栏目:百科 来源:网络整理
导读:正则表达式(Regular Expression, RE),就是用某种模式去匹配一类字符串的一个公式,据说最初来源于人工智能理论。它现在被各种文本编辑软件、类库、脚本工具(awk/grep/sed)等广泛支持,而且Microsoft的工具也开始支持。前段时间使用这些脚本工具完成过
正则表达式(Regular Expression, RE),就是用某种模式去匹配一类字符串的一个公式,据说最初来源于人工智能理论。它现在被各种文本编辑软件、类库、脚本工具(awk/grep/sed)等广泛支持,而且Microsoft的工具也开始支持。前段时间使用这些脚本工具完成过一个数据库文本文件的格式处理工作,感觉到正则表达式实在是太强大的,这个思想其实完全可以应用于C语言的开发,已经有可用的RE Library了。
在Linux下首先要理解一下元字符的概念。元字符是一类表达的是不同于字面本身含义的字符。有两类元字符:shell的元字符和正则表达式元字符,它们是各司其职的。shell元字符由Unix/Linux的shell来解析,正则表达式的元字符则是由各种执行模式匹配操作的程序来解析,如vi、grep、sed、awk(bash提供了一套模式匹配元字符,类似于grep、sed、awk所使用的正则表达式的元字符,但是还是有所不同的。)
举个简单的例子。比如“*”星号。shell中的星号是通配符,表示匹配0个或者多个任意字符,而RE中的星号则是表示匹配0个或多个相同的前导字符。所以,在RE中,常常用“.*”来表示0个或者多个任意字符。
也就是说,你首先要对比分清shell下的元字符和RE下的元字符并非相同,不可混用。使用工具来应用RE时,要注意使用单引号把正则表达式括起来,防止被shell当作其元字符解析。如果能够区分了,那么还要理解RE也并非是完全一样。现在的正则表达式有两套字符集,一套是基本元字符集,另一套是扩展元字符集。POSIX标准为此提供了一套RE标准。也就是各个工具首先是支持基本元字符集,但是并不一定支持所有的扩展元字符集,这就是实现相关的问题了。需要针对使用RE的工具(比如vim、grep、sed、awk、perl、python等)的帮助文档,看看它所支持的RE元字符集有那些,然后才能设计出合理高效的正则表达式,完成自己的工作。
到这里,基本上把正则表达式的概念,来源,与shell的区别,本身的不同实现都搞清楚了。这样,也就不会询问这个正则表达式在sed下好用,但是使用vim就不行,为什么?很简单,元字符的支持问题。

正则表达式有最长匹配的特性,也就是贪婪规则。

正则表达式是具有贪婪性的,我们从下面这例谈起:
已知str="uid=100(guest) gid=100(others) groups=10(users),11(floppy)"
现在想要得到这个字符串中的第一个括号内的值,即guest该怎么办?假设$str的括号外的内容是不固定的,不能依据uid之类的关键字或空格去查找,所依据的只能是找第一对括号内的内容。

很自然的我们想到用sed,因为sed具有很强的模式匹配的功能,而且能够将匹配的部分内容强行记下来用于输出。这样,我们就会想:
echo $str|sed 's/模式串/1/'
只要模式串写好了,在匹配的过程中把guest这个字符串抠出来,让sed记住,然后用1输出就可以了。怎么写这个模式串?
.*想要匹配"uid=100"
(...)告诉sed要查找括弧内的文本
(.*)让sed记住匹配内容的常用手段,这里匹配的.*将来就能用1取出来
.*想要匹配" gid=100(others) groups=10(users),11(floppy)"部分
于是我们就写成了echo $str|sed 's/.*(.*).*/1/'
结果呢,得到的是"floppy",为什么?
正则表达式是有贪婪性的,它总是与最长的可能长度匹配,而且越是排在前面的通配符优先级越高。这一例,第一个.*可以匹配"uid=100(guest) gid=100(others) groups=10(users),11",仍然能保证后面通配符的匹配,那一对()匹配了floppy左右的括号,最后的.*自然是可有可无的,所以sed记住的就是floppy。
怎么办?我们必须打破正则表达式的贪吃性,用更明确的描述来实现这一点:
我们考虑如果在模式串中第一个.*中告诉sed这个.*是不能含有"("的,不久可以将.*限制到"uid=100"了吗?这个意思我们完全可以用[^(]*来表达,于是我们修订刚才的代码,变成:
echo $str|sed 's/[^(]*(.*).*/1/'
似乎应该很好了,执行的结果却是"guest) gid=100(others) groups=10(users),11(floppy",为什么?
原来仍然是正则表达是的贪婪性在作怪,虽然我们有效的阻止了第一个.*的贪吃,但是我们对(.*)中的.*却未加限制,于是它尽可能匹配了"guest) gid=100(others) groups=10(users),11(floppy",还能保证后面").*"的匹配性。我们再作限制,告诉sed,(.*)中的.*不能含有")",让它跨不过guest:
echo $str|sed 's/[^(]*([^)]*).*/1/'
这回,输出结果终于是我们想要得"guest"了。

问题解决了,我们也了解了正则表达式(或说通配符)的贪婪性,于是就可以留个问题给大家,让大家自己体会体会:
怎么样取出str中第二对括号的内容"others"?
怎么样取出str中第三对括号的内容"users"?
怎么样取出str中第四对括号的内容"floppy"?(这个还用说吗,就利用正则表达式的贪婪性,我们最开始不就实现它了嘛)

又见michaelds的佳作。
俺来做作业。
str="uid=100(guest) gid=100(others) groups=10(users),11(floppy)"
echo $str|sed 's/[^(]*([^)]*)[^(]*([^)]*)[^(]*([^)]*)[^(]*([^)]*
)/2/'
上式得到others,把2改成3,4可得到users和floppy。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读