sed和python正则表达式之间的不一致

时间:2012-08-23 21:58:35

标签: python regex sed

我道歉,如果这是在某处发布的,但我的粗略搜索没有找到任何东西。

在做一些Python编程时,我注意到以下命令:

re.sub("a*((ab)*)b", r"\1", "aabb")

返回空字符串。但是sed中的等效命令:

echo "aabb" | sed "s/a*\(\(ab\)*\)b/\1/"

返回ab

我觉得python正则表达式开头的“a *”指令会匹配a两个,导致“(ab)*”匹配零次,但我不知道sed如何提出ab。有谁知道造成这种情况的两个正则表达式引擎之间有什么区别?我相信它们都默认贪婪地匹配星星,但我发现sed可能从右边而不是左边匹配。任何见解将不胜感激。

2 个答案:

答案 0 :(得分:4)

默认情况下Python和sed都是贪婪的但是...... Python正则表达式尝试在所有情况下从左到右进行评估,尽管如果尝试的分支无法通过匹配继续,它必须最终回溯到先前的状态。 相反,在评估之前优化Sed正则表达式,以便通过将正则表达式重写为更确定的形式来防止不必要的回溯。因此,组合的可选模式“aab”可能在普通“a”之前进行测试,因为首先尝试了最具体的可能字符串。

Python模式将字符串“aabb”两次匹配为“aab”+“b”(标记为“<>”)

>>> re.sub("a*((ab)*)b", r"<\1>", "aabb")
'<><>'

而sed通过一次替换匹配整个“aabb”:

$ echo "aabb" | sed "s/a*\(\(ab\)*\)b/<\1>/"
<ab>

Python正则表达式回溯算法在regex howto - Repeating Things中用“逐步示例......”一词引入的两段中解释得很好。 IMO完全按照regex docs描述的内容:“扫描目标字符串时,RE由'|'分隔从左到右尝试。“

示范

“(| a | aa)”btw的顺序。 “(aa | a |)”受到Python的尊重

>>> re.sub("(?:|a|aa)((ab)*)b", r"<\1>", "aabb")
'<ab>'
>>> re.sub("(?:aa|a|)((ab)*)b", r"<\1>", "aabb")
'<><>'

但是sed 忽略了这个顺序,因为sed优化了正则表达式。匹配“aab”+“b”可以重现从模式中删除“a”选项。

$ echo "aabb" | sed "s/\(\|a\|aa\)\(\(ab\)*\)b/<\2>/g"
<ab>
$ echo "aabb" | sed "s/\(aa\|a\|\)\(\(ab\)*\)b/<\2>/g"
<ab>
$ echo "aabb" | sed "s/\(aa\|\)\(\(ab\)*\)b/<\2>/g"
<><>

修改:我删除了有关DFA / NFA的所有内容,因为我无法从当前文本中证明这一点。

答案 1 :(得分:2)

你构建的有趣的谜题。从我读过的文章来看,python和sed的regexp引擎都基于Henry Spencer的正则表达式库(就像perl一样),它依赖于回溯。 (不幸的是我找不到我正在基于此的文章)。

无论如何,这是某些应该是实现细节的东西:Python的行为违背了POSIX标准,这要求RE在(a)尽可能匹配,以及(b)匹配从该点开始的最长字符串。 (请参阅man 7 regex(在Linux上)以及更多内容。)

要找到最长的匹配项,回溯(“NFA类型”)正则表达式引擎必须在找到一个匹配项后继续检查备用项。因此,实施者偷工减料也就不足为奇了。显然,python的行为是不符合的,因为它找不到最长的匹配。根据sed手册页,sed并不总是符合“出于性能原因”。但显然这种情况是正确的。

顺便提一下,你的命令并不完全等同:re.sub会尽可能多地执行替换,而sed的s/a/b/只会执行一次.sed版本应该是:

echo "aabb" | sed "s/a*\(\(ab\)*\)b/\1/g"

这解释了为什么我们在python中得到空字符串:RE第一次匹配aab而第二次匹配剩余的b,删除每个部分(因为它全部匹配a*和正则表达式的最终b。您可以通过以下变体看到这一点:

>>> re.sub("a*((ab)*)b", r"X\1Y", "aabb")
'XYXY'