在Vim中使用quickfix列表进行搜索和替换

49

到目前为止,我一直使用EasyGrep来替换多个文件中的文本。不幸的是,当项目变得更大时,它相当慢。似乎非常快的一件事是fugitive.vim的Ggrep,它只搜索我的版本控制文件。所有结果也存储在quickfix列表中。

如何使用Ggrep的结果对所有找到的文件进行简单替换?是否可以在quickfix列表中的所有文件上使用%s/foo/bar/cg,或者有更好的方法吗?

6个回答

76

更新: 现在Vim有了cdo,请参见Sid的答案

原始答案:

Vim有bufdo, windo, tabdoargdo,这让您可以在所有打开的缓冲区、窗口或文件中执行相同的命令。我们真正需要的是像quickfixdo这样的东西,它会在快速修复列表中的每个文件上调用命令。不幸的是,Vim缺乏这种功能,但这里有一个解决方案,由Al提供了一个自制的解决方案。使用这个,我们可以运行:

:QFDo %s/foo/bar/gc

这将对快速修复列表中的所有文件运行foo/bar替换。

bufdowindotabdoargdo命令有一些共同的行为。例如,如果当前文件无法放弃,则所有这些命令都将失败。我不确定上面提到的QFDo命令是否遵循相同的约定。

我改编了Al的解决方案创建了一个名为Qargs的命令。运行此命令会使用快速修复列表中列出的所有文件填充参数列表:

command! -nargs=0 -bar Qargs execute 'args ' . QuickfixFilenames()
function! QuickfixFilenames()
  " Building a hash ensures we get each buffer only once
  let buffer_numbers = {}
  for quickfix_item in getqflist()
    let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
  endfor
  return join(values(buffer_numbers))
endfunction

使用此功能,您可以按照以下步骤进行项目范围的搜索和替换:
:Ggrep findme
:Qargs
:argdo %s/findme/replacement/gc
:argdo update

编辑:(感谢Peter Rincker)

或者您可以将最后3个命令合并到一行中:

:Ggrep findme
:Qargs | argdo %s/findme/replacement/gc | update

我认为你应该在Qargs命令中使用-nargs=0-bar - Peter Rincker
1
使用argsargdo的思路非常优雅!我认为将Qargsargdo命令组合成一个命令会更好(更方便):command! -nargs=1 -complete=command -bang Qargdo exe 'args '.QuickfixFilenames() | argdo<bang> <args> - ib.
3
жҲ‘е·Із»ҸеҲӣе»әдәҶдёҖдёӘз®ҖеҚ•зҡ„жҸ’件жқҘеҢ…иЈ…:Qargsе‘Ҫд»Өпјҡhttps://github.com/nelstrom/vim-qargsгҖӮ - nelstrom
9
@ib,感谢您的帮助(当然也要感谢@nelstrom)。我fork了@nelstrom的插件并添加了您的“Qargdo”,但为了避免命令完成冲突,将其命名为“Qdo”:https://github.com/henrik/vim-qargs - Henrik N
4
已提交一个补丁以向 Vim 添加 :cdo 和 :ldo 命令。 - Fernando Correia
应该在答案中注明:hidden需要打开(可能作为给初学者的提示,在结尾处)。 - AlexMA

64

cdo命令已经添加! 在您使用grep之后,您可以使用cdo将给定的命令执行到快速修复列表中的每个术语:

cdo %s/<search term>/<replace term>/cg

(查看此Git提交和此vim开发人员Google讨论组以获取有关和添加它背后的动机的更多信息。)


3
我认为替换命令不应包含“%”,以防止替换选定列表之外的行。此外,如果替换不是幂等的,可能会导致意外结果。 - magras
使用 cdo 还可以打开更灵活操作的惊人选项。例如 cdo norm capMy new paragraph,此示例在每个搜索词出现时运行 normal,将搜索词周围的整个段落更改为“我的新段落”。令人惊叹的强大。 - Noel Evans
2
我还需要将上述内容传输到“update”命令中,否则vim不会移动到下一个文件,因为缓冲区已被编辑但未保存。命令:cdo %s/<search term>/<replace term>/gc | update - antisa

16

nelstrom的回答很全面,反映了他在vimdom方面的杰出贡献。它还略微超出了这里所需的范围;可以省略快速修复步骤,而选择用shell命令的结果填充args:

:args `git grep -l findme`
:argdo %s/findme/replacement/gc
:argdo update

这应该是你所需要的全部内容。

编辑: 正如Domon指出的,如果尚未设置,必须首先执行:set hidden!


1
+1 针对实用解决方案。基于此,您还可以在启动 vim 时填充 arglist:vim \git grep -l findme`` - Stephen Emslie
2
这对我有效。但是,在执行 :argo ... 之前,我必须先执行 :set hidden,否则 Vim 会因为未保存的更改而出现错误。 - Domon

5
使用 quickfix-reflector.vim,您可以编辑快速修复窗口中的搜索结果。然后write命令将保存更改到您的文件中。
:copen
:%s/foo/bar/cg
:write

4

外部grep

(使用类似于makeprg/errorformat的grepprg和grepformat;如果grepprg =='internal'则与内部grep相同)

:grep fopen *.c
:copen
:cnext

Internal grep

:vimgrep /\<myVimregexp\>/ **/*.c
:copen
:cnext

位置列表内部grep
:lvimgrep /\<myVimregexp\>/ **/*.c
:lopen
:lnext

奖励:对加载的缓冲区进行外部grep:

等等。

:silent bufdo grepadd fstream %
:copen
:cnext

所有参数均为外部参数:

等等。

:silent argdo grepadd fstream %
:copen
:cnext

我不确定你想用那些例子表达什么意思。我知道如何使用grep和vimgrep。我不明白这个答案与问题的关系。 - medihack
1
(a) 展示几种在vim中缩小grep范围的方法 (b) 展示如何在外部grep中执行此类操作。如果有点无聊,我很抱歉,但这对我很有效 :) - sehe

1

有一个补丁可以将cdo(Quickfix do)命令添加到vim中,但尚未被合并(截至2015年3月25日):

https://groups.google.com/forum/#!topic/vim_dev/dfyt-G6SMec

你可能需要自己打补丁来修复vim,以获取此补丁:
brew install hg # install mercurial, e.g. with homebrew
hg clone https://vim.googlecode.com/hg/ vim
cd vim
# copy/download patch to . folder
patch -b -p1 < cdo.diff
./configure
make && make install

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接