Vim 正则表达式负环视和捕获组

Vim Regex Negative Look Arounds and Capture Groups

假设您有以下文字

foobar
bar

并且您希望将以下内容作为您想要的输出

foobar
foobar

您可以使用以下正则表达式

s/\v(foo)@<!(bar)/foo/g

我之前犯的错误是认为 bar 的反向引用是 </code> 而不是 <code>;我不认为正则表达式环顾四周被认为是捕获组。现在让我感兴趣的是,如果你 使用 </code>。您将获得的输出如下</p> <pre><code>foobar foo

使用上述逻辑,如果 </code> 引用第一个捕获组 <code>(foo),那么我预计输出将是

foobar
foofoo

稍微考虑一下后,我怀疑这个问题的答案是因为它使用的是负面回顾,所以它只在指定文本时捕获 foo 存在。因此,这意味着存储的捕获组什么都不是。只是一个空字符。如果 </code> 是指定的反向引用,这将导致 <code>foo 成为输出。我的推论正确吗?

让我相当确定的是,如果我要更改正则表达式以使用正向后视而不是引用第一个捕获组,如下所示

s/\v(foo)@<=(bar)/foo/g

输出将变为

foofoo
bar

意思是因为它是 positive 后视,当 foo 出现时捕获组 (foo) 匹配,因此存储的捕获组必须是foo.

这种混淆的根源是 Perl 正则表达式以正则表达式环视 的方式工作的事实作为捕获组。如果我上面所说的是正确的,我很好奇为什么 vim 正则表达式和 Perl 正则表达式之间存在这种差异。

I'm curious as to why there is this difference between vim regex and Perl regex.

因为它们是两个不同的正则表达式引擎。如果它们以完全相同的方式工作,就不会有 Vim 正则表达式引擎和 Perl 正则表达式引擎,它们都是 Perl 正则表达式引擎。

在某些时候™,Vim 制作了一个正则表达式引擎并决定了某些事情。显然,其中之一是将前瞻作为捕获组。如果您想进一步讨论与 Perl 的分歧,@<= 允许在 Vim 中使用非固定宽度模式,但在 Perl(和其他几个引擎)中不允许。这就是它的设计方式。 “为什么”只有做出来的人才能明确回答,我就不回答了。


如果您绝对想将该组从组计数中排除,您可以根据 :h /\%(\) 添加 % 前缀,使其成为非捕获组(即 s/\v%(foo)@<!(bar)/foo/g) .请注意,非捕获组仍然表现正常,但在替换时不能引用它们。

虽然我已经在写答案了,但让我向您介绍 \zs\ze,这是迄今为止对 Vim 正则表达式引擎的最佳补充之一(在我的偏见):

\zs 定义实际匹配的开始位置。它不会影响组,但它有几个有用的副作用。具体在您的情况下,它可以让您完全放弃正面的回顾。它不会让你放弃负面的回顾(因为正则表达式),但它会让你稍微简化你的正则表达式。等价地,\ze 确定匹配结束的位置。

你的第二个例子可以简化为:

s/\vfoo\zs(bar)/

\zs 告诉引擎在 (bar) 之前开始匹配。如果有帮助,您可以将每个正则表达式视为以 \zs 为前缀并以 \ze 为后缀 - 明确定义它只会改变这些界限。这不会影响号码分组和 \<n>-保存。

这意味着只有 bar 选择的 space 被认为是匹配项,并且该位被替换 - 其他位保持不变。

你的第一个负向后视的正则表达式也没有简化(因为正则表达式总体感觉是为正向操作而设计的,所以任何向后操作的东西都会变得混乱),但对于更长的正则表达式,它仍然可以显着缩短正则表达式.这是替换的样子:

s/\v(foo)@<!\zebar/foo

展开:

s/\v
  | (foo)@<!
  | |       \ze
  | |       |   bar
  | |       |   |  /foo
  ^ Very magic  |  |
    ^ not prefixed with foo. Can be made non-capturing, but it has no actual relevance for this regex specifically
            ^ End the match
                ^ bar
                   ^ substitute the "area" selected by "not prefixed with foo" with foo

('请原谅这张糟糕的图表,我以前从未制作过其中一个,我不记得它们通常是如何制作的)

这个使用 \ze 因为你的目标是间接地用它自己替换由负前瞻分配的 space。不幸的是,Vim 只存储实际匹配的值,这意味着 </code> 不能用于插入 <code>foo,因为它还不存在。这可能是所有引擎都会做的事情,因为您无法猜测实例 (?<=ab.d) 的内容。


话虽这么说,如果您只是想避免与组编号混淆,非捕获组是目前的方法。 \zs\ze 虽然很棒,但一开始有点令人困惑,暂时可能不是学习 Vim 中其他所有内容的最佳主意。

最后,一个意想不到的插件推荐:haya14busa/incsearch.vim(没有隶属关系,只是一个用户),它可以预览您的替换和搜索,这样您就可以知道接下来会发生什么在您继续进行替换或搜索之前发生。可能无助于解决您对组编号的困惑,但您至少能够在替换之前看到您使用了错误的组编号。