我试图了解Tcl子表达式匹配和"贪婪"并且我完全不知道发生了什么。引用http://wiki.tcl.tk/396上的示例:
%regexp -inline (.*?)(n+)(.*) ennui
en e n {}
%regexp -inline ^(.*?)(n+)(.*)$ ennui
ennui e nn ui
尽管我不完全理解"嵌套表达式" (这是括号表示的,对吗?)匹配,我决定从小开始,尝试将*和+之间的区别作为贪婪的运算符:
% regexp -inline (.*)(u*)(.*) ennui
ennui ennui {} {}
% regexp -inline (.*)(u+)(.*) ennui
ennui enn u i
如果*匹配零或更多,+匹配一个或多个,我不明白两个命令之间输出的差异。为什么u *和u +在同一个字符串上产生两个不同的结果?
我觉得这是一个非常重要的细微差别 - 如果我能够掌握这个简单模式匹配/正则表达式中发生的事情,我的生活将会变得完整。救命!
提前致谢。
答案 0 :(得分:5)
关于非贪婪。 Tcl正则表达式有一个怪癖:表达式中的第一个量词设置了整个表达式的贪婪。 (请参阅re_syntax
manual page的“匹配”部分,密切关注“偏好”一词:)
分支具有与其中具有偏好的第一个量化原子相同的偏好。
%regexp -inline (.*?)(n+)(.*) ennui
en e n {}
(.*?)
抓取零个或多个字符,更喜欢最短匹配(n+)
抓取一个或多个n
,继承最短的偏好(.*)
抓取零个或多个字符,继承最短的偏好第一个子表达式匹配第一个字符,但不包括第一个n
。第二部分匹配一个n
。第三部分匹配第一个和第二个n
之间的零个字符。
我有点惊讶的是,第一个子表达式捕获了e
而不是在第一个n
之前捕获零个字符,但这可以通过“最左边”匹配到的最高优先级来解释正则表达式引擎:
如果RE可以匹配给定字符串的多个子字符串,则RE匹配字符串中最早开始的字符串。
achored表达式的结果也让我感到惊讶:我原本期望e n nui
而不是e nn ui
。添加$
锚似乎已经放弃了表达式对最短匹配的偏好。
答案 1 :(得分:3)
(.*)(u*)(.*)
和(.*)(u+)(.*)
差异的原因是第二个正则表达式至少需要1个u
。
Tcl中的ARE正则表达式使用回溯(与大多数NFA一样)。使用(.*)
,引擎从头到尾抓取整个字符串,并开始回溯以查找它是否可以容纳下一个子模式。
在第一个表达式中,u
是可选的(由于*
可以为0),因此,贪婪的.*
决定它不会产生任何字符。然后,最后.*
也可以匹配0个字符,同样,不需要为该组提供任何字符。
在第二个表达式中,u
是强制性的,必须至少出现一次。因此,引擎会使用第一个.*
抓取所有字符串,然后回溯并找到u
。因此,它将起始序列放入组1,并将u
与(u+)
进行匹配和捕获。由于u
仅为1,因此最后(.*)
匹配并捕获字符串的其余部分。
答案 2 :(得分:0)
@stribizhev答案几乎解释了一切。至于你的非贪婪版本 - 最后的问号告诉引擎它不应该消耗整个字符串,但抓住尽可能少的匹配并从那里继续。
(.*?) for "ennui"
匹配0个字符,没关系,因为我们不贪心(n+) for "ennui"
匹配失败,因此引擎再次返回匹配(.*?)
(.*?) for "ennui"
现在匹配一个字符e
(n+) for "nnui"
匹配nn
,因为它贪婪(.*) for "ui"
匹配剩下的ui