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
.
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
- StackOverflow using vimgrep and cfdo
- StackOverflow post using vimgrep and cfdo
- Vimregex cheatsheet
- How to close all open buffers in Vim
- Case insensitive search in vim
Further Reading
- The Best Way to Learn Vim
- How to use allure-pytest and allure-pytest-bdd in the same project
- Google Search Console API with Python
- What I learned optimising someone else’s code
- Deploying Dremio on Google Cloud
- Gitmoji: Add Emojis to Your Git Commit Messages!
- Five Tips to Elevate the Readability of your Python Code
- Do Programmers Need to be able to Type Fast?
- How to Manage Multiple Git Accounts on the Same Machine