免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 9962 | 回复: 50
打印 上一主题 下一主题

看看这个正则的匹配结果 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-07-15 16:42 |只看该作者 |倒序浏览
5可用积分
123456789
正则为((?<=\d)(\d{3}))*
我觉得是匹配5678,但是实际结果是空

最佳答案

查看完整内容

比较有趣的现象,看起来像是perl的BUG。问题出现在字符串起始位置,反向预查如果加上量词"*"会匹配空,即非贪婪匹配。如:但其它位置好像就没问题,如:目前只好建议你改写一下正则来避免这个问题,如这样:—————呵呵,楼主提的真是个好问题啊,我前面的回答却不太正确。不好意思看来得继续分析一下,要不对不起楼主的悬赏分数啊!我在27楼的说法前面部分大体上是对的,但由于没有对具体例子中的匹配过程一步一步仔细分析,所 ...

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
2 [报告]
发表于 2008-07-15 16:42 |只看该作者
原帖由 xiaothree 于 2008-7-15 18:09 发表
test]$   echo 123456789|perl -pe 's/((?


比较有趣的现象,看起来像是perl的BUG。
问题出现在字符串起始位置,反向预查如果加上量词"*"会匹配空,即非贪婪匹配。如:
  1. $ echo 123456789|perl -pe "s/(?<=^)((?<=\d)(\d{3}))*/_$&_/"
  2. __123456789
复制代码

但其它位置好像就没问题,如:
  1. $ echo 123456789|perl -pe "s/(?<=.)((?<=\d)(\d{3}))*/_$&_/"
  2. 1_234567_89
复制代码


目前只好建议你改写一下正则来避免这个问题,如这样:
  1. $ echo 123456789|perl -pe "s/(?<=\d)(\d{3})*/_$&_/"
  2. 1_234567_89
复制代码






—————
呵呵,楼主提的真是个好问题啊,我前面的回答却不太正确。不好意思
看来得继续分析一下,要不对不起楼主的悬赏分数啊!

我在27楼的说法前面部分大体上是对的,但由于没有对具体例子中的匹配过程一步一步仔细分析,所以推论是有问题的。

我把27楼的部分内容抄在下面:
根据perl的正则引擎(使用NFA)的匹配顺序,引擎依次从字符串读入一个字符,看是否与正则式匹配。引擎缺省地采用贪婪匹配的方式,即尽可能地找到最长的匹配,这意味着找到一个匹配后引擎并不会立即停止,而是读入更多的字符,试图找到更长的匹配,直到字符串的字符被消耗完,如果这时不匹配,引擎会吐回一个字符再检查,这叫做回溯。
...

这里有两个关键的概念:贪婪匹配和回溯。

NFA缺省是贪婪匹配的,在有匹配时会继续读入字符试图匹配更长的字串,这一点没问题;但是有一点很关键:在遇到回溯时,如果已经有了匹配,NFA引擎会直接采用已有的匹配,而不再尝试新的(也许是更好的匹配)。就是说已有匹配的前提下,回溯后不再尝试新的匹配。这点很重要,请牢记,在下面的分析中会多次用到。

由于这个特点,NFA引擎可能会错失一些更长的匹配,我们来看这个例子:


  1. $ echo Alice and tom have a tomcat|perl -pe 's/(tom|tomcat)/<$1>/'
  2. Alice and <tom> have a tomcat
复制代码

看到了吗?perl匹配了先出现的较短的tom而错失了更长的tomcat。

我们来分析一下匹配的过程:

1.前面的匹配过程我说得简略点吧。perl读入字符尝试匹配但一直失败,直到读入字符tom,这时找到了第一个子表达式对应的匹配,"tom"。
2.由于贪婪性,匹配并不停止,perl读入下一个字符“ ”即空格,这时两个子式都不匹配,所以回溯,“吐回”一个字符,因为前面已经有了一个匹配,所以就采用那个匹配即“tom”,返回。更长的第二个子式就得不到匹配了。

由这个例子可以看出:NFA有可能错过一些可能的匹配,有时甚至是更好的匹配,这是NFA本身的匹配方法固有的弱点。我们在写表达式时要注意避免这一弱点,例如上面的例子,如果我们把较长的子式“tomcat”写在前面,类似(tomcat|tom)这样,它就会优先得到匹配了。

下面我们再来分析楼主的例子:
  1. $ echo 123456789|perl -pe "s/((?<=\d)(\d{3}))*/_$&_/"
  2. __123456789
复制代码


表达式匹配了空串,看起来有点奇怪。让我们一步一步分析一下:

1.在读入第一个字符以前,即现在我们处于串首,可以匹配“^”。这时perl尝试与表达式匹配,非常不幸,确实有一个不太好的匹配:((?<=\d)(\d{3}))的0次出现,即空串。
2.由于贪婪性,perl继续尝试匹配,读入第一个字符"1",第一个子式“(?<=\d)”要求当前位置前面是一个数字,匹配成功,但第二个子式匹配失败,应该回溯,回吐字符“1”。
3.回溯后,因为前面已经有一个匹配,perl返回那个匹配,完成匹配。
这样perl就作出了一个似乎不符合贪婪性的匹配--空串。

我上面给出的第一个例子也类似:
  1. $ echo 123456789|perl -pe "s/(?<=^)((?<=\d)(\d{3}))*/_$&_/"
  2. __123456789
复制代码


1.在读入第一个字符以前,perl尝试与表达式匹配,同样有一个鸡肋的:(?<=^)加((?<=\d)(\d{3}))的0次出现,也是一个空串。
2.由于贪婪性,perl继续尝试匹配,读入第一个字符"1",第一个子式“(?<=^)”要求当前位置前面是串首,但却是字符1匹配失败,应该回溯,回吐字符“1”。
3.回溯后,因为前面已经有一个匹配,perl返回那个匹配-—空串,完成匹配。

再看楼主另一个例子,用量词+代替了量词*:
  1. $ echo 123456789|perl -pe "s/((?<=\d)(\d{3}))+/_$&_/"
  2. 1_234567_89
复制代码

匹配返回了符合贪婪性的结果234567。分析:

1.同样在读入第一个字符以前,perl尝试与表达式匹配,但这次量词不是*而是+,所以匹配失败。
2.因为前面没有成功的匹配,所以perl读入第一个字符"1",再尝试匹配,这时第一个子式(?<=\d)匹配,但第二个子式不匹配。
3.因为前面没有成功的匹配所以继续读入第二个字符“2”,仍旧没有匹配。
4.依次类推直到读入字符“4”,这时有一个匹配“234”,perl记下这个状态。
5.由于贪婪性,perl继续尝试匹配,读入第下个字符"5",这时有两个匹配“234”和“345”。
6.依次类推直到读入字符“7”,这时有匹配“234”,“345”,“456”,“567”和一个长匹配“234567”。
7.由于贪婪性,perl继续尝试匹配,读入下个字符"8",这时有匹配:“234”,“345”,“456”,“567”,“678”,“234567”和“345678”。
8.perl继续读入下个字符"9",这时有匹配:“234”,“345”,“456”,“567”,“678”,“789”,“234567”,“345678”和“456789”。此时字符串耗尽,perl返回第一个最长的匹配“234567”,匹配结束。
这里的关键之处是读入第一个字符前的尝试匹配失败,所以perl必须继续读入新字符尝试成功的匹配。

再看我第二个例子:
  1. $ echo 123456789|perl -pe "s/(?<=.)((?<=\d)(\d{3}))*/_$&_/"
  2. 1_234567_89
复制代码

同样读入第一个字符前的尝试匹配失败,因为第一个子式“(?<=.)”要求前面是任意一个字符,不匹配。于是perl只好继续读入新字符尝试成功的匹配。直到读入最后一个字符,然后返回第一个最长匹配“234567”。

再看我给楼主建议的第三个例子:
  1. $ echo 123456789|perl -pe "s/(?<=\d)(\d{3})*/_$&_/"
  2. 1_234567_89
复制代码

其中去掉了包围两个子式的小括号。

同样读入第一个字符前的尝试匹配失败,因为第一个子式“(?<=\d)”要求前面是一个数字,匹配失败。同样perl只好继续读入新字符尝试成功的匹配。直到读入最后一个字符,然后返回第一个最长匹配“234567”。

总结一下:

1.NFA(为大多数程序采用)下量词*和+是贪婪匹配的,在匹配成功的前提下引擎会不断读入下一个字符尝试新的匹配,直到匹配失败或字符串耗尽。这样通常会返回尽可能长的匹配。

2.匹配失败后引擎会回溯,回溯后如果前面已经有了成功的匹配,引擎会直接采用已有的匹配,不再尝试新的匹配。这样常常会因此错失可能有的更长的匹配。所以我们写正则时要注意避免这种情况发生。上面提到了两种情况:
其一,并列的子式,比较长的最好写在前面;
第二,为了避免*量词错误地匹配0次出现,应该仔细编写表达式,避免在读入第一个字符前就造成已有空匹配。

按照上面的分析方法再看看29楼walkerxk的例子就很清楚了,(12)*会造成读入第一个字符前有空匹配,(12)的0次出现。所以必然第一次就会只匹配到空串;12*不会造成读入第一个字符前有匹配所以不会第一次就只匹配到空串,例子中第一次实际上会匹配到1。



注意:discuz好像会过滤$,所以上面用了全角,粘贴代码时请改成半角

[ 本帖最后由 woodie 于 2008-7-17 00:59 编辑 ]

论坛徽章:
0
3 [报告]
发表于 2008-07-15 17:12 |只看该作者
walkerxk@www:~$ echo 123456789|perl -pe 's/(?<=\d)(\d{3})/a/'
1a56789
walkerxk@www:~$ echo 123456789|perl -pe 's/(?<=\d)(\d{3})*/a/'
1a89
walkerxk@www:~$
你的写法不对,而且怎么可能是匹配5678呢?应该是匹配第一个数字后的位置和234三个数字。

论坛徽章:
0
4 [报告]
发表于 2008-07-15 17:18 |只看该作者

回复 #1 xiaothree 的帖子

这得看具体是哪个工具的正则。
[cocobear@cocobear test]$ echo "123456789" | grep -e '((?<=\d)(\d{3}))*'
[cocobear@cocobear test]$

论坛徽章:
0
5 [报告]
发表于 2008-07-15 17:25 |只看该作者
(?<=exp)  零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp
((?<=\d)(\d{3}))* 这个其实是 其自身((\d{3}))*)出现的位置前面能匹配 一个数字,对于字串 123456789,(\d{3}))* 表示一个3位数字出现0次我>0次,为贪婪匹配,那么字串末尾9为exp,其自身为空,所以这个正则匹配为空,
你改成 若匹配5678 你用 ((?<=\d{4})(\d{4}))  试一下

论坛徽章:
0
6 [报告]
发表于 2008-07-15 18:09 |只看该作者
test]$   echo 123456789|perl -pe 's/((?<=\d)(\d{3}))+/a/'
1a89                                       ------贪婪匹配,匹配最大(2次)

test]$   echo 123456789|perl -pe 's/((?<=\d)(\d{3}))+?/a/'
1a56789                                 ------非贪婪匹配,匹配最小(1次)

上面两个在意料之中,

test]$   echo 123456789|perl -pe 's/((?<=\d)(\d{3}))*/a/'
a123456789                           ------按理也应该是贪婪匹配,为什么就匹配空呢?

论坛徽章:
0
7 [报告]
发表于 2008-07-15 18:19 |只看该作者
是perl还是shell啊

论坛徽章:
0
8 [报告]
发表于 2008-07-15 18:28 |只看该作者
perl

论坛徽章:
0
9 [报告]
发表于 2008-07-15 20:21 |只看该作者

不知道对不对

#!/usr/bin/perl -w
while(<>){
        chomp;
        $_=~/\S((?<=\d)(\d{3}))*/;
        print "$&\n";
}


测试文件内容是123456789

$&是匹配到的内容
运行结果是
[root@youzi perl-study]# ./decimal.pl decimal.txt
1234567

论坛徽章:
0
10 [报告]
发表于 2008-07-15 20:25 |只看该作者
暈死了,我都看不明白,回去看書了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP