如何使 vim 替换重复直到没有更多匹配项?

How do I make vim substitute repeat until there are no more matches?

我有一个文件,其文本看起来像

{\o H}{\o e}{\o l}{\o l}{\o o} {\o W}{\o o}{\o r}{\o l}{\o d}

我想让文字看起来像这样

{\o Hello} {\o World}

可能有多种工具可以用来解决这个问题,但我正在尝试使用 vim 替换。到目前为止,我有

:%s/{\o \(.\{-}\)}{\o \(.\{-}\)}/{\o }/g

这里的图案是

.\{-}匹配部分字符,非贪心

{\o .\{-}} 匹配正则表达式字符串 {\o .*} 但与 .*

不匹配

\( ... \) 为反向引用创建捕获组。

当我 运行 这个替换一次时,我得到

{\o He}{\o ll}{\o o} {\o Wo}{\o rl}{\o d}

可以运行这个命令再执行两次得到

{\o Hell}{\o o} {\o Worl}{\o d}

然后

{\o Hello} {\o World}

但如果有一种方法可以用一个命令做到这一点,我真的很高兴,即告诉 vim 替换继续传递文件,直到没有更多匹配项为止。在我看来,这应该是可能的。

有谁知道我需要添加什么魔法才能实现这一目标?看起来没有实现这一目标的标志。也许我不知道标签的一些技巧?

子替换表达式方法

您可以使用子替换表达式 \=,在 :s

的替换部分内执行第二次替换
:%s/\({\o \S}\)\+/\='{\o '.substitute(submatch(0), '{\o \(\S\)}', '', 'g').'}'/g

基本思路是捕获 "word",例如{\o H}{\o e}{\o l}{\o l}{\o o},然后对捕获的"word"、submatch(0)进行第二次替换。第二次替换将通过 substitute(submatch(0), '{\o \(\S\)}', '', 'g') 删除开头的 {\o 和结尾的 }。这只留下字母,在本例中为 Hello。一旦从单词中提取出每个字母,我们就会在开头 {\o 和尾随 }.

中添加回去

如需更多帮助,请参阅:

:h :s
:h sub-replace-expresion
:h substitute(
:h submatch(
:h literal-string
:h /\S

两次换人

您也可以使用 2 个单独的替换。一个删除所有 {\o}。另一个替换将把它们加回去,但这次是逐字的。

:%s/{\o \(.\)}//g
:%s/\S+/{\o &}/g

假设您没有将此文本与不应采用此格式的其他文本混合在一起,这可以说是一种更简单的方法。

您也可以使用 |:

在一个命令中完成此操作
:%s/{\o \(.\)}//g|%s/\S+/{\o &}/g

这里有一个更简单的解决方案:

递归宏。递归宏的基本思想是,一个宏会调用自身直到发生错误。当您 运行 替换命令并且没有匹配项时,这将引发错误。所以这是你需要做的基本布局。

  1. 清除宏'q'。这是必不可少的,这样它就不会在第一个 运行 通过时做任何奇怪的事情。您可以使用

    qqq
    

    :let @q=""
    

    两者都有效。

  2. 开始录制。这只是:

    qq
    
  3. 运行 你的替代命令,然后调用宏。这将是

    :%s/{\o \(.\{-}\)}{\o \(.\{-}\)}/{\o }/g<cr>@q
    

    因为 @q 是 运行 宏的命令。 这不会在您录制时执行任何操作,因为您已经清除了寄存器'q'。最后,

  4. 停止录制,调用宏。

    q@q
    

    这将循环您刚才按下的一组击键,直到没有匹配项为止。

综合起来:

qqqqq:%s/{\o \(.\{-}\)}{\o \(.\{-}\)}/{\o }/g<cr>@qq@q

开头是 5 'q' 秒。您还可以从命令行分配寄存器 'q',其中:

:let @q=':%s/{\o \(.\{-}\)}{\o \(.\{-}\)}/{\o }/g<cr>@q'

然后点击

<cr>@q

作为旁注,我强烈推荐dedicated vim-site