如何在vim中使用ack进行搜索和替换?

60
我正在使用Vim中的Ack插件,它可以帮助我快速搜索项目中的字符串。但是,有时我想替换所有或某些出现的字符串。您可以使用Vim arglist 进行一些全局搜索和替换,像这样(source):
:args app/views/*/*
:argdo %s/, :expire.*)/)/ge | update

但是我不想使用args,而是更喜欢通过Ack进行搜索,然后在找到的所有文件中进行替换。有没有类似于argdo命令的方法可以实现呢?

5个回答

98

我决定在Vim之外使用ackperl来解决这个问题,这样我就可以使用更强大的Perl正则表达式而不是GNU子集。你可以将它映射到你的.vimrc中的一个按键。

ack -l 'pattern' | xargs perl -pi -E 's/pattern/replacement/g'

解释

ack

ack是一个强大的命令行工具,结合了grepfind和完整的Perl正则表达式(不仅仅是GNU子集)。它完全由Perl编写,速度快,支持高亮匹配,可在Windows上运行,并且比传统的命令行工具更加友好。在Ubuntu上安装它可以使用sudo apt-get install ack-grep命令。

xargs

xargs是一个古老的Unix命令行工具。它从标准输入中读取项并执行指定的命令,然后将从标准输入读取的项目附加到命令末尾。因此,通过ack生成的文件列表被追加到perl -pi -E 's/pattern/replacement/g'命令的末尾。

perl -pi -E

Perl是一种编程语言。

-p选项使Perl在程序周围创建一个循环,该循环遍历文件名参数。

-i选项使Perl原地编辑文件。你可以修改这个选项以创建备份。

-E选项使Perl执行指定作为程序的一行代码。在我们的例子中,程序只是一个Perl正则表达式替换。

有关Perl命令行选项的更多信息,请参见perldoc perlrun。有关Perl的更多信息,请参见http://www.perl.org/


7
我稍微修改了我的解决方案,使用sed,因为perl没有安装: ack -l OLD_TEXT | xargs sed -i "" "s/OLD_TEXT/NEW_TEXT/g" - tommy chheng
@Eric Johnson,你为什么在这里使用了-E开关来运行perl?我看不出任何特性的用途。 - Gilles Quénot
2
@sputnick,我只是出于习惯一直使用-E选项,这样我就可以使用“say”命令了。当然,-e也同样有效。 - Eric Johnson
1
除非我告诉ack只列出带有-l的文件,否则它会为我抛出错误,就像这样:ack -l 'pattern' | xargs perl -pi -E 's/pattern/replacement/g' - jpadvo
1
@tommychheng 你如何在没有perl的情况下运行ack? - a3f
显示剩余2条评论

53

现在,Vim有一个新命令cdo,可以将给定命令运行到快速修复列表的每一行。

因此,您可以使用

:Ack pattern
:cdo s/pattern/newpattern/g

这应该是被接受的答案。非常好用,谢谢! - ZiGiUs
自从vim 7.4版本以来,这就是正确的答案。对于OP来说,稍微调整一下,使用:cfdo可能更合适。 - gregory
我可能错过了一些显而易见的东西,但我在 Fedora 上运行 Vim 7.4,每当我尝试使用 :cdo 命令时都会出现 not an editor command 的错误提示,有什么想法吗? - ohhh
从帮助页面得知,你需要使用编译了 +listcmds 功能的 Vim。这是否可以解释缺失的命令? - rools
3
":cdo s/pattern/newpattern/g | update" 对我有效。 (注:该命令为Linux/Unix系统中的文本编辑命令,意思是将当前文件中所有匹配 "pattern" 的字符串替换为 "newpattern" 并保存修改后的文件。) - Isaac Pak

16

我不认为有内置的方法可以做到这一点,但是制作一个应该很容易。

你需要做的是创建一个调用自定义函数的命令。该函数应该使用getqflist()函数获取快速修复列表中的所有条目,并使用exe执行相关操作。请注意传递参数时要小心!

" Define a command to make it easier to use
command! -nargs=+ QFDo call QFDo(<q-args>)

" Function that does the work
function! QFDo(command)
    " Create a dictionary so that we can
    " get the list of buffers rather than the
    " list of lines in buffers (easy way
    " to get unique entries)
    let buffer_numbers = {}
    " For each entry, use the buffer number as 
    " a dictionary key (won't get repeats)
    for fixlist_entry in getqflist()
        let buffer_numbers[fixlist_entry['bufnr']] = 1
    endfor
    " Make it into a list as it seems cleaner
    let buffer_number_list = keys(buffer_numbers)

    " For each buffer
    for num in buffer_number_list
        " Select the buffer
        exe 'buffer' num
        " Run the command that's passed as an argument
        exe a:command
        " Save if necessary
        update
    endfor
endfunction

1
假设您执行类似于 :QFDo v/skipPat/s/Pat/Rep/gce 的操作,并且快速修复窗口上有大量缓冲区。然后,在第一个文件中,您注意到替换字符串 Rep 打错了。按下 Ctrl-c 不会停止命令,而只会停止当前行上的替换命令 - 它需要在每个缓冲区上匹配的每一行都按下 Ctrl-c。是否有任何技巧可以捕获 Ctrl-cESC 键并打破上述函数中的 for 语句? - mMontu
请参考上面的答案,阅读这篇文章,该文章启发了这个插件的诞生。 - Fernando Correia

10

你可以这样使用ack

:args `ack -l User app/`
:argdo %s/, :expire.*)/)/ge | update

或使用ag

:args `ag -l User app/`
:argdo %s/, :expire.*)/)/gec | w

9

我使用MacVim(在终端中激活为mvim)。我将ack的结果传输到mvim

mvim -f $(ack -l $@)

在MacVim中,我使用bufdo进行搜索/替换:
:bufdo %s/SEARCH/REPLACE/gce | update

如果不需要确认,请省略 c 选项。

执行 vim -p $(ack -Qil "string to match") 命令,可以在单独的标签页中打开它们(如果你喜欢的话!) - tester

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