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

Regex Vim正则表达式负环视和捕获组,regex,vim,regex-lookarounds,regex-group,neovim,Regex,Vim,Regex Lookarounds,Regex Group,Neovim,我将试着用一个例子来解释我的问题 假设你有下面的文本 foobar bar 您希望以下内容作为所需的输出 foobar foobar 您可以使用以下正则表达式 s/\v(foo)@<!(bar)/foo\2/g 使用上述逻辑,如果\1引用第一个捕获组,(foo),那么我希望输出是 foobar foofoo 在考虑了一点之后,我怀疑这个问题的答案是,因为它是一个正在使用的反向查找,所以它仅在指定的文本foo不存在时捕获。因此,这意味着存储的捕获组什么都不是。只是一个空字符。如果\1

我将试着用一个例子来解释我的问题

假设你有下面的文本

foobar
bar
您希望以下内容作为所需的输出

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

s/\v(foo)@<!(bar)/foo\2/g
使用上述逻辑,如果
\1
引用第一个捕获组,
(foo)
,那么我希望输出是

foobar
foofoo
在考虑了一点之后,我怀疑这个问题的答案是,因为它是一个正在使用的反向查找,所以它仅在指定的文本
foo
不存在时捕获。因此,这意味着存储的捕获组什么都不是。只是一个空字符。如果
\1
是指定的反向引用,则这将导致输出为
foo
。我的推断正确吗

让我对此相当确定的是,如果我将regex改为使用积极的lookback,而不是引用第一个捕获组,如下所示

s/\v(foo)@<=(bar)/foo\1/g
这意味着,由于它是一个正向查找,所以当存在
foo
时,捕获组
(foo)
匹配,因此存储的捕获组必须是
foo

造成这种混乱的原因是Perl正则表达式的工作方式是不将正则表达式lookarounds作为捕获组包含。如果我在上面所说的是正确的,那么我很好奇为什么vim正则表达式和Perl正则表达式之间存在这种差异

我很好奇为什么vim正则表达式和Perl正则表达式之间存在这种差异

因为它们是两个不同的正则表达式引擎。我不完全理解您对这里的答案的期望,但是如果它们以完全相同的方式工作,就不会有Vim正则表达式引擎和Perl正则表达式引擎,它们都是Perl正则表达式引擎

在某个时候™, Vim制造了一个正则表达式引擎,并决定了某些事情。显然,其中之一就是将长相头作为捕获群体。如果您想进一步讨论与Perl的差异,
@
虽然我已经在写一个答案,但让我向您介绍
\zs
\ze
,这是迄今为止Vim regex引擎的最佳补充之一(我有偏见地认为):

\zs
定义实际匹配的开始位置。它不会影响群体,但有几个副作用。特别是在您的情况下,它可以让您完全放弃积极的回顾。它不会让你放弃消极的回顾(因为正则表达式),但它会让你简化一点正则表达式。等效地,
\ze
确定匹配的结束位置

您的第二个示例可以简化为:

s/\vfoo\zs(bar)/\1
\zs
告诉引擎在
(条形)
之前开始匹配。如果有帮助的话,您可以将每个正则表达式看作前缀为
\zs
,后缀为
\ze
——显式定义它只会更改这些边界。这不会影响号码分组和保存

这意味着只有由
条选择的空间被视为匹配,该位被替换,其他位保持不变

您的第一个具有负向后看的正则表达式也不会简化(因为正则表达式总体上感觉是用于向前操作的,所以任何向后操作都会变得混乱),但对于较长的正则表达式,它仍然可以显著缩短正则表达式。以下是这种替代的情况:

s/\v(foo)@<!\zebar/foo
s/\v(foo)@
扩大:

s/\v
|(富)@
(‘根据这张糟糕的图表,我以前从未做过一张,我不记得它们通常是怎么做的)

这一个使用
\ze
,因为您的目标是间接地用自身替换由负前瞻分配的空间。不幸的是,Vim只存储实际匹配的值,这意味着
\1
不能用于插入
foo
,因为它还不存在。这可能是所有引擎都会做的,因为你猜不到
(?)的内容?
s/\vfoo\zs(bar)/\1
s/\v(foo)@<!\zebar/foo