How to do search & replace with ack in vim?
Asked Answered
B

5

60

I am using the Ack plugin in Vim, which helps me to quickly search for strings in my project. However, sometimes I want to replace all or some occurrences of the found strings. You can do some kind of global search and replace using the Vim arglist like this (source) :

:args app/views/*/*
:argdo %s/, :expire.*)/)/ge | update

But instead of using args, I would prefer to do a search via Ack and then do the replace in all files that have been found. Is there a way to do it similar to the argdo command?

Brassy answered 25/1, 2011 at 10:50 Comment(0)
W
98

I've decided to use ack and perl to solve this problem outside of Vim so I could use the more powerful Perl regular expressions instead of the GNU subset. You could map this to a key stroke in your .vimrc.

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

Explanation

ack

ack is an awesome command line tool that is a mix of grep, find, and full Perl regular expressions (not just the GNU subset). It's written in pure Perl, it's fast, it has match highlighting, it works on Windows and it's friendlier to programmers than the traditional command line tools. Install it on Ubuntu with sudo apt-get install ack-grep.

xargs

xargs is an old Unix command line tool. It reads items from standard input and executes the command specified followed by the items read for standard input. So basically the list of files generated by ack are being appended to the end of the perl -pi -E 's/pattern/replacement/g' command.

perl -pi -E

Perl is a programming language.

The -p option causes Perl to create a loop around your program which iterates over filename arguments.

The -i option causes Perl to edit the file in place. You can modify this to create backups.

The -E option causes Perl to execute the one line of code specified as the program. In our case the program is just a Perl regex substitution.

For more information on Perl command line options, see perldoc perlrun. For more information on Perl, see http://www.perl.org/.

Winchell answered 5/1, 2012 at 14:28 Comment(7)
+1 I slightly my solution to use sed since perl wasn't installed: ack -l OLD_TEXT | xargs sed -i "" "s/OLD_TEXT/NEW_TEXT/gFrizzell
@Eric Johnson, why did you use -E switch for perl here ? I see no use of any featureRochelle
@sputnick, I just always use -E out of habit so I have 'say' available. -e works just as well of course.Winchell
This throws errors for me unless I tell ack to list just the files with -l like this: ack -l 'pattern' | xargs perl -pi -E 's/pattern/replacement/g'Bereave
@tommychheng How do you run ack without perl?Carl
This should added on the Documentation section. What nice line of script.Marou
is it somehow possible to ask for confirmation for every change?Tamathatamaulipas
P
53

Now, Vim has this new command cdo that will run the given command to each line of the quickfix list.

So you can use

:Ack pattern
:cdo s/pattern/newpattern/g
Petulance answered 24/8, 2016 at 9:15 Comment(5)
This should be the accepted answer. Works great, thanks!Intreat
Since vim 7.4, this is the right answer. One slight tweak, for the OP, :cfdo might be more appropriate.Upturned
i may be missing something obvious, but I'm running Vim 7.4 on fedora and I get not and editor command whenever I try :cdo, any ideas?Altogether
From the help page, you need your vim compiled with the +listcmds feature. Does it explain the missing command?Petulance
:cdo s/pattern/newpattern/g | update worked for me.Phlegethon
C
16

I don't believe there's a built in way of doing this, but it should be easy to make one.

What you need to do is create a command that calls a custom function. The function should then use the getqflist() function to get all of the entries in the quickfix list and exe to do the dirty work. Be careful what you pass as an argument!

" 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
Court answered 25/1, 2011 at 12:12 Comment(2)
Suppose you do something like :QFDo v/skipPat/s/Pat/Rep/gce and there is a large list of buffers on quickfix window. Then on the first file you notice you mistyped the replacement string Rep. Typing Ctrl-c won't stop the command, but only the replace command on current line - it will require a Ctrl-c for every line matched on each buffer. Is there any trick to catch the Ctrl-c or ESC key and break the for statement on function above?Terle
See also this article based on the answer above, which gave origin to this plugin.Challah
D
10

You could using ack by this way

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

Or use ag

:args `ag -l User app/`
:argdo %s/, :expire.*)/)/gec | w
Daughtry answered 31/3, 2013 at 12:56 Comment(0)
E
9

I use MacVim (activated with mvim in a shell). I pipe the results of ack to mvim:

mvim -f $(ack -l $@)

Then in MacVim, I search/replace using bufdo:

:bufdo %s/SEARCH/REPLACE/gce | update

Omit the c option if confirmation is not needed.

Eavesdrop answered 25/4, 2012 at 19:24 Comment(1)
do vim -p $(ack -Qil "string to match") to get these to launch in individual tabs (if that's your thing!)Hatteras

© 2022 - 2024 — McMap. All rights reserved.