在搜索模式中使用Vim / sed正则表达式反向引用

4

Vim帮助文档中写道:

\1      Matches the same string that was matched by     */\1* *E65*
        the first sub-expression in \( and \). {not in Vi}
        Example: "\([a-z]\).\1" matches "ata", "ehe", "tot", etc. 

看起来在搜索模式中可以使用反向引用。我开始尝试并注意到了无法解释的行为。这是我的文件:

<paper-input label="Input label"> Some text </paper-input>
<paper-input label="Input label"> Some text </paper-inputa>
<aza> Some text </az>
<az> Some text </az>
<az> Some text </aza>

我希望匹配开闭标签相匹配的行,例如:
<paper-input label="Input label"> Some text </paper-input>
<az> Some text </az>

我的测试正则表达式是:

%s,<\([^ >]\+\).*<\/\1>,,gn

但这匹配了行:134。使用 sed 也是同样的道理:

$ sed -ne 's,<\([^ >]\+\).*<\/\1>,\0,p' file
<paper-input label="Input label"> Some text </paper-input>
<aza> Some text </az>
<az> Some text </az>

这个正则表达式:<\([^ >]\+\)应该是贪婪的,当试图在末尾不加\1时,则所有组都正确。但是当我添加\1时,似乎<\([^ >]\+\)变得不贪婪了,它试图强制匹配第三行。有人能解释为什么它匹配第三行吗?

<aza> Some text </az>

这也是一个regex101演示

注意:这不是关于正则表达式本身的(可能有其他方法可以做到),而是关于该正则表达式的行为。


4
你应该查看回溯引擎。如果它找不到匹配项,引擎会回溯并选择其他内容。例如,在所有回溯之后,第三行中的\1等于az。(因为你从未添加锚点) - FDinoff
1
补充@FDinoff的观点,您可以添加一个规则来匹配空格或>作为锚点... <\([^ >]\+\)[ >].*<\/\1> - Sundeep
@FDinoff 这很有趣。我不知道这个。 - Dave Grabowski
1
@spasic 是的,我理解回溯是如何工作的,对于空格和>的锚点似乎是最好的想法。 - Dave Grabowski
@FDinoff 如果您将此作为答案添加,我会标记。 - Dave Grabowski
3个回答

4
为了理解正则表达式的行为,您需要了解回溯正则引擎的工作原理。
该引擎会贪婪地匹配和消耗尽可能多的字符。但是,如果它没有找到匹配项,则会返回并尝试查找仍满足模式的不同匹配项。
%s,<\([^ >]\+\).*<\/\1>,,gn

对于第三行<aza> Some text </az>

正则表达式引擎查看\1 = aza。并查看.*</aza>是否匹配剩余字符串。它不匹配,因此它选择其他值用于\1。下一次它选择\1 = az并查看.*</az>是否匹配剩余的字符串,它匹配了。所以字符串匹配了。

(这是简化版本。我跳过了.*本身可能会有很多回溯的事实)


解决问题就像在正则表达式中添加锚点一样容易,这样可以防止正则表达式继续搜索其他可能满足\1的值。在这种情况下,匹配空格或>就足够了。


这是非常好的解释。像@LucHermitte建议的那样,以\>结尾的单词(即<\([^ >]\+\)\>.*</\1>)也可以起作用。 - Dave Grabowski

2

您需要添加\>来表示单词的结尾。可能还有其他使用0宽度模式的解决方案,但这会使事情变得更加复杂。

另外,您的分隔符是,,而不是/

因此,正确的写法应该是:

%s,<\([^ >]\+\)\>.*</\1>,,gn

这不会匹配第一行。此外,正如我在问题中提到的 - 我想知道为什么我的正则表达式未起作用。 - Dave Grabowski
1
我刚刚检查了一下。这与第一行相匹配(我刚刚检查了我手头的gvim 7.4-2207和vim 7-4-2181)。但是Regex 101处理不好它。 关于解释,@FDinoff已经给出了。 - Luc Hermitte
@DawidGrabowski, 这在vim 7.3-429上也能正常工作。难道你改变了&isk的定义吗? - Luc Hermitte
不是复制正则表达式,而是手写它并犯了一个错误。它正在工作中(使用sed和vim)。 - Dave Grabowski
那就发生了 :) - Luc Hermitte

0

目前第3行(<aza>)显示为匹配的原因是您的正则表达式中的.*可以跨多行匹配。因此,第3行匹配,因为第5行有闭合标签。要纠正这个问题,请强制正则表达式仅在同一行上查找匹配的闭合标签:

%s,<\([^ >]\+\)[^\n]*?<\/\1>,,gn
               ^^^^^ use [^\n]* instead of .*

你为什么认为.*会跨越多行匹配?它匹配除了换行符以外的任何字符。 - Dave Grabowski
@DawidGrabowski那么你如何解释第三行显示为匹配项? - Tim Biegeleisen
我不知道。这就是为什么我问这个问题的原因。我知道.*绝对不匹配换行符。我添加了regex101演示 - Dave Grabowski
1
\_. 在 vim 中可以匹配换行符,但 . 不能。 - Luc Hermitte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接