【问题标题】:How to search and replace with a counter-based expression in Vim?如何在 Vim 中使用基于计数器的表达式进行搜索和替换?
【发布时间】:2011-05-09 20:51:58
【问题描述】:

有没有办法在 Vim :substitute 命令中插入某种计数器变量的值?

例如,要转换此文档:

<SomeElement Id="F" ... />
<SomeElement Id="F" ... />
<SomeElement Id="F" ... />

到这个生成的文档:

<SomeElement Id="1" ... />
<SomeElement Id="2" ... />
<SomeElement Id="3" ... />

我想,这个命令应该是这样的:

:%s/^\(\s*<SomeElement Id="\)F\(".*\)$/\1<insert-counter-here>\2/g

我使用的是最新的 Windows 版本,来自他们提供的安装程序。我强烈不想安装任何额外的工具。理想情况下,我也希望避免安装脚本来支持这一点,但如果这是唯一的方法,我愿意这样做。

【问题讨论】:

    标签: regex vim counter replace


    【解决方案1】:

    可以使用用表达式替换的计数器 功能(请参阅:help sub-replace-\=)。不幸的是,由于 \= 构造只允许表达式,:let 命令不能 被使用,因此不能以通常的方式设置变量。

    但是,有一个简单的技巧可以改变变量的值 如果该变量是列表或字典,则表达式。在这种情况下, 其内容可以通过map()函数修改。

    以这种方式,替换问题中描述的情况 如下所示:

    :let n=[0] | %s/Id="F"/\='Id="'.map(n,'v:val+1')[0].'"'/g
    

    这里的棘手部分是替换的替代部分。 因为它以\= 开头,所以它的其余部分被解释 作为 Vim 的表达式。因此,'Id="'.map(n, 'v:val+1').'"' 是一个普通的表达。这里是字符串文字'Id="' 与返回值连接(使用. 运算符) 函数调用map(n, 'v:val+1') 和另一个 字符串,'"'map 函数需要两个参数: 一个列表(在这种情况下)或一个字典,以及一个包含 应该为给定中的每个项目评估的表达式 列表或字典。特殊变量v:val 表示个体 项目清单。所以'v:val+1' 字符串将被评估为一个列表项 加一。

    在这种情况下,我们甚至可以进一步简化命令:

    :let n=[0] | %s/Id="\zsF\ze"/\=map(n,'v:val+1')[0]/g
    

    \zs\ze 模式原子用于设置开始和 要替换的模式的结尾,分别(参见:help /\zs:help /\ze)。这样整个搜索部分的替代品 命令匹配,但只有\zs\ze 之间的部分是 更换。这避免了替代中的笨拙连接 表达。

    这两个简短的单行代码中的任何一个都可以完全解决问题。

    对于频繁的替换,甚至可以定义一个辅助函数

    function! Inc(x)
        let a:x[0] += 1
        return a:x[0]
    endfunction
    

    并使替换命令更短:

    :let n=[0] | %s/Id="\zsF\ze"/\=Inc(n)/g
    

    【讨论】:

    • +1。我将留下第一个接受的答案,因为它是第一个并且有效(并且可能在不同场景中也有用),但我承认这与我的想法更接近。赞一个!
    • @Merlyn 谢谢!对我来说,这几乎是在需要计数器时进行替换的惯用方式(在列表中添加行号、替换 id 等)。
    • 多余的单引号是怎么回事?我在另一个答案中问过,但仍然无法弄清楚(通过盯着它):)
    • @Merlyn 我已经为我的答案添加了更多解释。
    • @ib:哇,这个问题提供了大量信息。 \zb\ze 将在我的许多搜索中摆脱大量零宽度断言开销。我希望我可以发布答案赏金作为提示;)
    【解决方案2】:

    Vim wiki instructions 似乎是最简单的解决方案(至少对我而言)。

    下面的示例将所有出现的PATTERN 替换为REPLACE_[counter]REPLACE_1REPLACE_2 等):

    :let i=1 | g/PATTERN/s//\='REPLACE_'.i/ | let i=i+1
    

    要回答这个问题,它可能看起来像这样:

    :let i=1 | g/SomeElement Id="F"/s//\='SomeElement Id="'.i.'"'/ | let i=i+1
    

    替代解决方案

    如果有人对%s 语法的解决方案感兴趣,我建议查看@ib。答案是:

    :let n=[0] | %s/Id="\zsF\ze"/\=map(n,'v:val+1')/g
    

    【讨论】:

    • 比第一个答案更好,因为它不会与键绑定混淆,并以更简单的方式显示正在发生的事情。与 anubina 提交的版本相同,但也链接到半官方文档。
    • 但我不明白,如何将它与'&lt;,'&gt; 又名当前选择一起使用。 find-replace 的模式看起来很奇怪(例如,g 在开头,s 在中心,这是怎么回事??),有没有办法让它正常工作s/from/to 命令?
    • 我只是讨厌这里的正则表达式语法与 %s vim 替换中使用的语法不同
    • @jmarceli 你能在你的回答中取悦%s吗?
    • 用数字填充的简单方法是什么?所以 1, 2, 3 会变成 001, 002, 003?
    【解决方案3】:

    嗯,这有点棘手。这是我到目前为止得到的。在 vim 会话中尝试这 2 个 map 命令:

    :nmap %% :let X=1<cr>1G!!
    :nmap !! /^\s*<SomeElement Id="F"<cr>:s/F"/\=X.'"'/<cr>:let X=X+1<cr>!!
    

    一旦出现,请按%% 开始有趣的部分:)

    它使您给定的文件为:

    <SomeElement Id="1" ... />
    <SomeElement Id="2" ... />
    <SomeElement Id="3" ... />
    <SomeElement Id="4" ... />
    

    说明:

    第一个 nmap 命令正在将以下序列映射到击键%%

    • 将变量 X 初始化为 1
    • 移动到第一个文件的开头
    • 调用另一个映射的击键!!

    第二个 nmap 命令正在将以下序列映射到击键!!

    • 搜索下一个出现的模式^\s*&lt;SomeElement Id="F"
    • 如果找到上述模式,则搜索并替换 F" 变量 X 和引号 "
    • 将 vim 变量 X 增加 1
    • 通过调用!! 递归调用自身
    • 单点.在vim中用于串接字符串,和php很像

    当模式 ^\s*&lt;SomeElement Id="F" 不再在文件中找到时,此递归调用停止。

    【讨论】:

    • +1。这很有帮助!不过,我的实际文件中还有其他行。在执行s/F"/\=X.'"'/ 之前如何前进到正确的行。另外,.'"' 部分有什么作用?
    • 酷,它起作用了,一旦我想出(或多或少)将j 替换为:/F"&lt;cr&gt; :) 你能逐个添加脚本吗?我理解,但我认为添加到答案中会是一件很棒的事情。
    • 添加了解释并对第二个 map 命令进行了一些修改,以确保通过仅在模式 ^\s*&lt;SomeElement Id="F" 匹配的行中增加数字来替换 F"
    • 不好意思忘了加点(.)是vim中用来连接字符串的,和php很像。
    • @anubhava 我会映射其中一个功能键,因为它们不会发生冲突 (:h map-which-keys)。
    【解决方案4】:

    非常简单的解决方案。我不得不这样做几次。

    :let i=1 | g/^\(\s*<SomeElement Id="\)F\(".*\)$/s//\=submatch(1).i.submatch(2)/ | let i=i+1
    

    基于以下提示。 http://gdwarner.blogspot.com/2009/01/vim-search-and-replace-with-increment.html

    【讨论】:

      【解决方案5】:

      把它放在你的 vimrc 中或在你当前的会话中执行它:

      function! Inc(x) 
        let a:x[0] += 1 
        return a:x[0] 
      endfunction
      
      function IncReplace(pos, behind, ahead, rep) 
        let poss=a:pos-1 
        let n=[poss] 
        execute '%s/' . a:behind . '\zs' . a:rep . '\ze' . a:ahead . '/\=Inc(n)/g' 
      endfunction
      

      然后执行:call IncReplace(1, 'Id="', '"', 'F')

      第一个参数是你想从哪个数字开始,第二个是你想在数字后面匹配什么,第三个是你想在数字前面匹配什么,第四个是你真正想要替换的。

      【讨论】:

        【解决方案6】:

        也许plugin increment.vim 会有所帮助

        【讨论】:

          猜你喜欢
          • 2015-02-16
          • 2011-06-15
          • 2011-11-02
          • 1970-01-01
          • 2015-02-15
          • 2014-04-29
          • 1970-01-01
          • 1970-01-01
          • 2018-04-25
          相关资源
          最近更新 更多