Search and replace in Vim across all the project files
VimReplaceVim Problem Overview
I'm looking for the best way to do search-and-replace (with confirmation) across all project files in Vim. By "project files" I mean files in the current directory, some of which do not have to be open.
One way to do this could be to simply open all of the files in the current directory:
:args ./**
and then do the search and replace on all open files:
:argdo %s/Search/Replace/gce
However, when I do this, Vim's memory usage jumps from a couple dozen of MB to over 2 GB, which doesn't work for me.
I also have the EasyGrep plugin installed, but it almost never works—either it doesn't find all the occurrences, or it just hangs until I press CtrlC. So far my preferred way to accomplish this task it to ack-grep for the search term, using it's quickfix window open any file that contains the term and was not opened before, and finally :bufdo %s/Search/Replace/gce
.
I'm looking either for a good, working plugin that can be used for this, or alternatively a command/sequence of commands that would be easier than the one I'm using now.
Vim Solutions
Solution 1 - Vim
The other big option here is simply not to use vim:
sed -i 's/pattern/replacement/' <files>
or if you have some way of generating a list of files, perhaps something like this:
find . -name *.cpp | xargs sed -i 's/pattern/replacement/'
grep -rl 'pattern1' | xargs sed -i 's/pattern2/replacement/'
and so on!
Solution 2 - Vim
EDIT: Use cfdo
command instead of cdo
to significantly reduce the amount of commands that will be run to accomplish this (because cdo
runs commands on each element while cfdo
runs commands on each file)
Thanks to the recently added cdo
command, you can now do this in two simple commands using whatever grep
tool you have installed. No extra plugins required!:
1. :grep <search term>
2. :cdo %s/<search term>/<replace term>/gc
3. (If you want to save the changes in all files) :cdo update
(cdo
executes the given command to each term in the quickfix list, which your grep
command populates.)
(Remove the c
at the end of the 2nd command if you want to replace each search term without confirming each time)
Solution 3 - Vim
I've decided to use ack and Perl to solve this problem in order to take advantage of the more powerful full Perl regular expressions rather than the GNU subset.
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). Its written in pure Perl, its fast, it has syntax highlighting, works on Windows and its 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/replacemnt/g'
command.
perl -pi
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 perldoc perlrun
. For more information on Perl see http://www.perl.org/.
Solution 4 - Vim
Greplace works well for me.
There's also a pathogen ready version on github.
Solution 5 - Vim
maybe do this:
:noautocmd vim /Search/ **/*
:set hidden
:cfirst
qa
:%s//Replace/gce
:cnf
q
1000@a
:wa
Explanation:
:noautocmd vim /Search/ **/*
⇒ lookup (vim
is an abbreviation forvimgrep
) pattern in all files in all subdirectories of the cwd without triggering autocmds (:noautocmd
), for speed's sake.:set hidden
⇒ allow having modified buffers not displayed in a window (could be in your vimrc):cfirst
⇒ jump to first search resultqa
⇒ start recording a macro into register a:%s//Replace/gce
⇒ replace all occurrences of the last search pattern (still/Search/
at that time) withReplace
:- several times on a same line (
g
flag) - with user confirmation (
c
flag) - without error if no pattern found (
e
flag)
- several times on a same line (
:cnf
⇒ jump to next file in the list created by thevim
commandq
⇒ stop recording macro1000@a
⇒ play macro stored in register a 1000 times:wa
⇒ save all modified buffers
*** EDIT *** Vim 8 way:
Starting with Vim 8 there is a better way to do it, as :cfdo
iterates on all files in the quickfix list:
:noautocmd vim /Search/ **/*
:set hidden
:cfdo %s//Replace/gce
:wa
Solution 6 - Vim
:args
from a shell command
Populate It's possible (on some operating systems1)) to supply the files for :args
via a shell command.
For example, if you have ack2 installed,
:args `ack -l pattern`
will ask ack to return a list of files containing 'pattern' and put these on the argument list.
Or with plain ol' grep i guess it'd be:
:args `grep -lr pattern .`
You can then just use :argdo
as described by the OP:
:argdo %s/pattern/replacement/gce
:args
from the quickfix list
Populate Also check out nelstrom's answer to a related question describing a simple user defined command that populates the arglist from the current quickfix list. This works great with many commands and plugins whose output ends up in the quickfix list (:vimgrep
, :Ack
3, :Ggrep
4).
The sequence to perform a project wide search could then be done with:
:vimgrep /pattern/ **/*
:Qargs
:argdo %s/findme/replacement/gc
where :Qargs
is the call to the user defined command that populates the arglist from the quickfix list.
You'll also find links in the ensuing discussion to simple plugins that get this workflow down to 2 or 3 commands.
Links
- :h {arglist} - vimdoc.sourceforge.net/htmldoc/editing.html#{arglist}
- ack - betterthangrep.com/
- ack.vim - github.com/mileszs/ack.vim
- fugitive - github.com/tpope/vim-fugitive
Solution 7 - Vim
One more option in 2016, far.vim plugin:
Solution 8 - Vim
1. :grep <search term> (or whatever you use to populate the quickfix window)
2. :cfdo %s/<search term>/<replace term>/g | update
Step 1 populates the quickfix list with items you want. In this case, it's propagated with search terms you want to change via grep
.
cfdo
runs the command following on each file in the quickfix list. Type :help cfdo
for details.
s/<search term>/<replace term>/g
replaces each term. /g
means replace every occurrence in the file.
| update
saves the file after every replace.
I pieced this together based upon this answer and its comments, but felt it deserved its own answer since it's all in one place.
Solution 9 - Vim
If you don't mind of introducing external dependency, I have brewed a plugin ctrlsf.vim (depends on ack or ag) to do the job.
It can format and display search result from ack/ag, and synchronize your changes in result buffer to actual files on disk.
Maybe following demo explains more
Solution 10 - Vim
- Make sure you’re using Neovim (or Vim 7.4.8+, but really just use Neovim)
- Install FZF for the command line and as a vim plugin
- Install Ag, so that it’s available automatically to FZF in vim
- If using iTerm2 on OSX, set the alt/option key to Esc+
Usage
Search the text you want to change in the current directory and it’s children with
:Ag text
- Keep typing to fuzzy filter items
- Select items with alt-a
- Deselect items with alt-d
- Enter will populate the quickfix list
- :cfdo %s/text/newText/g | :w
Now you have chabges made inside Vim NeoVim
Solution 11 - Vim
You can do it with shell and vim's ex-mode. This has the added benefit of not needing to memorize other search-and-replace escape sequences.
Any command that can list files will work (rg -l
, grep -rl
, fd
...). For example in bash:
for f in $(rg -l Search); do
vim -Nes "$f" <<EOF
%s/Search/Replace/g
wq
EOF
done
You can use any command, those prefixed with :
in command mode, the same way you would inside vim, just drop the :
at the start
Solution 12 - Vim
Basically, I wanted the replace in a single command and more importantly within vim itself
Based on the answer by @Jefromi i've created a keyboard shortcut, which I had set in my .vimrc file like this
nmap <leader>r :!grep -r -l * \| xargs sed -i -e 's///g'
now from the vim, on a stroke of leader+r I get the command loaded in vim, which i edit like below,
:!grep -r -l <find> <file pattern> | xargs sed -i -e 's/<find>/<replace>/g'
Hence, I do the replace with a single command and more importantly within vim itself