Summary workflow

There are many different ways in vim to search and replace across multiple files in the project, but I find a combination of :vimgrep and :cfdo to be most useful.

Combining vimgrep and cfdo

Find all instances of search string in the project
:vimgrep /search_string/gj **/*

Check quickfix list is correct
:copen

Find and replace and save files
:cfdo %s/search_string/replace_string/g | update

Find and replace across all project files in vim using vimgrep and cfdo

The first step is to use vim’s inbuilt tool :vimgrep to search for text patterns in files.

:vimgrep /search_string/gj **/*

This command will find all instances of the ‘search_string’ in your project files and adds them to the ‘quickfix’ list.

The command does not make any changes to the text. It just finds the location of each match and stores it in the quickfix list to be acted upon later.

The g flag searches for all occurances in a given line. The j means vim should just update the quickfix list and not actually jump to the file with the first match.

The **/* means to search all files recursively. This can be modified to search in specific directories or file types only. For example, to search in just python files you could use **/*.py. Or to search all files except .sh files you could use **/*[^.sh].

Note

Vimgrep accepts regular expressions for a given search string.

However, vim has it’s own flavour of regex which differs slightly to the regex accepted by other command line tools such as grep.

A good cheat sheet for vim regular expression syntax can be found here: https://vimregex.com/

It is always a good idea to sense check the matches in the quickfix list which have been found by your :vimgrep command.

You can open the quickfix list by using:

:copen

This will open a buffer with a line for each match found by :vimgrep.

Example quickfix list
Example quickfix list displayed after running :copen

If you are happy with the matches you can then move on to the next command using :cfdo to actually search and replace the text.

:cfdo %s/search_string/replace_string/gc | update

:cfdo will execute the specified command in each file in the quickfix list.

We can use vim’s standard search/replace syntax %s.

The c flag will make the command interactive and you will be asked before each match whether you want to make the change. If you have checked the quickfix list first and are confident in the match you shouldn’t need to use this flag.

Vim will carry out the search and replace in all files, but will not actually save the files after making the change. You can add the update command after the :cfdo to save each file after making the changes.

Finally, some optional clean up.

Vim will open up a buffer for each file that was affected by the search and replace command.

If you have a big project with lots of affected files, you will suddenly have all of these files now open in your buffers. To clean up and close all open buffers except your current buffer, you can use the following command.

:%bd|e#|bd#

This is not the most memorable command so it can be handy to create a mapping for this in your vim config.

Notes

  • If you have set ignorecase = true in your config you will need to prefix your search string with \C to be case sensitive. E.g. :vimgrep /\Csearch_string/gc **/*
  • Vim regular expressions are slightly different to normal regex. If you only want to search for complete words and not substrings you should use \< and \> around your search string. E.g. :vimgrep /\<word\>/gc **/*

Alternative option: Find and replace files in vim using args and argdo

As with most things in vim, there are multiple different ways of achieving the same outcome.

Another popular suggestion on various forums is to use :args and :argdo instead.

This would work something like:

# open relevant files into the vim buffer
:args `grep "search_string" . -R`

# search and replace with argdo
:argdo %s/search_pattern/replace_pattern/ge | update

It is quite similar to the previous approach. But this time we use :args along with the command line tool grep to find all files with the search string pattern.

This populates the argument list with all matches. Then we use :argdo to apply the search replacement command to all files in the argument list.

I don’t think there is anything particularly wrong with this approach, however, I think a big downside is that the command line tool grep uses a different flavour of regex to the %s command you will be using next. Therefore you might have to use two different regular expressions, one to populate the argument list with matches using grep, and a second using a vim regular expression. This could create room for mistakes if your grep regex command unknowingly produces a different result to the vim regex command.

With the first approach using vimgrep, both commands use vim regular expressions. Therefore, you can use the same regex command for both initially finding the matches and then carrying out the replacement. Reducing the chance of mistakes.

Happy coding!

Resources

Further Reading