找回密码
 注册
搜索
[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
查看: 26547|回复: 4

[转载教程] 论正则表达式的“贪婪”性:sed一例

[复制链接]
发表于 2011-6-2 00:14:04 | 显示全部楼层 |阅读模式
正则表达式是具有贪婪性的,我们从下面这例谈起:
已知str="uid=100(guest) gid=100(others) groups=10(users),11(floppy)"
现在想要得到这个字符串中的第一个括号内的值,即guest该怎么办?假设$str的括号外的内容是不固定的,不能依据uid之类的关键字或空格去查找,所依据的只能是找第一对括号内的内容。

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

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

原文:http://bbs.chinaunix.net/thread-15825-1-1.html

评分

参与人数 1技术 +1 收起 理由
batman + 1 好!学习了。。。

查看全部评分

发表于 2011-6-2 09:37:58 | 显示全部楼层
:这是显示第二个,如果是第10个呢?岂不是很麻烦?
  1. sed -n "s/^[^(]*([^(]*(\([^)]*\)).*/\1/p" a.txt
复制代码

还有,如果我是要提取字符串中的内容而不是括弧该怎么办?
比如: set str=abcd="123"fff hhh="difdf"abcd="456"fff fdfi"=k"abcd="789"fff
我要体取 123 456 789 该怎么办?
发表于 2011-6-2 09:56:49 | 显示全部楼层
本帖最后由 batman 于 2011-6-2 09:59 编辑

前提是要提取的字符串中没有@字符:
  1. sed -e "s/abcd=/@/g" -e "s/[^@]*@\x22\([^\x22]*\)\x22[^\x22@]*/\1/g"
复制代码
发表于 2011-6-2 13:37:43 | 显示全部楼层
又学了一手
发表于 2011-6-12 11:33:14 | 显示全部楼层
本帖最后由 tmplinshi 于 2011-6-12 18:45 编辑
:这是显示第二个,如果是第10个呢?岂不是很麻烦?
sed -n "s/^[^(]*([^(]*(\([^)]*\)).*/\1/p" a.txt
还有,如果我是要提取字符串中的内容而不是括弧该怎么办?
比如: set str=abcd="123"fff hhh="difdf"abcd=" ...
随风 发表于 2011-6-2 09:37


第 10 个:
  1. sed "s/(\([^)]*\))/\n&/10; s/.*\n(\([^)]*\).*/\1/"
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|批处理之家 ( 渝ICP备10000708号 )

GMT+8, 2026-3-17 05:12 , Processed in 0.019916 second(s), 9 queries , File On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表