首页 > 解决方案 > 您如何在 vim 可视模式下搜索/替换第 n 次出现?

问题描述

这有效:

'<,'>s/\v\/\zs(\/)// 
'<,'>s/\v(\/)@<=\//BAR/

我只是想知道是否有一种更简单的方法可以用{}vim 中的 a 或其他内容替换第 n 次出现。

替换第三个正斜杠“/”。

/dir1//fas//fooBar/¬
/dir2//\.foobar//fas/¬
/dir//.foo//fas/¬

我将如何替换第四个 'foo' ?

foo foo foo foo foo foo foo
foo foo foo foo foo foo foo

标签: regexvim

解决方案


为简单起见,我将讨论匹配这些模式,在替代命令中替换它们应该工作相同,在:s命令上使用相同的模式。

替换第三个正斜杠“/”。

使用一个字符匹配会更容易,因为您可以使用它[^/]来查找不属于匹配的字符。

如果要计算匹配项,则需要从行首开始,因此使用^.

此时,您可以匹配两个“非斜线”实例,后跟一个“斜线”,然后在第三个实例上,您可以使用 a\zs将其标记为实际匹配的开始。

不幸的是,如果我们在比赛中使用它,它/本身需要被转义\/,但结果模式是:

/\v^%([^\/]*\/){2}[^\/]*\zs\/

包含模式的一个常见技巧/是使用 向后搜索?,所以让我们这样做以提高可读性:

?\v^%([^/]*/){2}[^/]*\zs/

我在这里使用的一些可能不熟悉的模式模式项目是:

  • %(...):对模式进行分组,与创建捕获组相同(...)但不创建捕获组。
  • {2}: 匹配前面的模式两次。

请记住,我们将“verymagic”与 一起使用\v,因此上述大部分内容都不需要反斜杠。

我们可以采取一个简洁的捷径来缩短上面的模式(当我们查看较长单词的情况时,这将有助于我们),也就是说,如果你\zs的模式中有多个位置,那么最后一个匹配的位置将将定义比赛的实际开始。(见:help /\zs。)

所以我们可以将其简化为:

?\v^%([^/]*\zs/){3}

我们匹配“not slashes”,然后匹配“slash”三遍。\zs只会在最后一个(第三个)匹配上生效,所以你最终会匹配该行的第三个斜线。

现在让我们继续讨论更复杂的匹配单词的情况:

我将如何替换第四个 'foo' ?

在这里我们不能使用[^...]匹配“not foo”。我的意思是,我们可以使用类似的东西,\v([^f]|f[^o]|fo[^o])但随着你匹配的词的增长,它会迅速增长。还有更好的方法来做到这一点。

我们可以使用零宽度的负后视!看看:help /\@<!这个有趣的运算符。简而言之,它采用前面的原子(我们将在此处使用带有单词的组)并确保该项目与该位置的结尾不匹配。

所以我们可以使用这个:

/\v^%(%(.%(foo)@<!)*\zsfoo){4}

这里%(foo)@<!确保我们匹配的每个都.不会是最后一个ofoo这样我们就可以准确地计算出第一、第二、第三和第四个,foo并确保我们不会匹配第五个、第六个或第七个。

在这里,我们再次使用重复四次(找到第四场比赛)并拥有最后\zs一根棍子的技巧。

请注意,否定的后视很适合固定单词,但是如果您开始使用诸如*+等之类的多重单词,那么事情会变得更加复杂。查看对操作员的帮助以及它可能会变慢的警告。还有一个运算符的变体,它限制了它会返回多少个字符,在匹配固定单词时您并不严格需要它,但可能对更一般的匹配有所帮助。

这个测试用例的一个有趣的测试用例是具有重复的匹配,例如fofo,以及包含重复的文本,例如fofofofofofofo

事实上,对这些的测试让我看到上面的模式实际上更喜欢匹配第二个出现fofofo而不是第一个,如果那是fofo该行中的第四个出现。那是因为*运营商是贪婪的。我们可以通过使用{-}来解决这个问题,它匹配尽可能短的序列。

修复该错误,我们得到:

/\v^%(%(.%(foo)@<!){-}\zsfoo){4}

这足够通用,您可能可以与任何固定单词一起使用,甚至可以使用具有一些变体的模式(例如,大小写、复数、替代拼写等)


推荐阅读