How do I search and replace text across multiple files in Vim?
Answer
:args **/*.py | argdo %s/old/new/gc | update
Explanation
Vim can perform search-and-replace across multiple files without any plugins by combining the arglist with :argdo. You load files into the argument list, then run a substitution on every file in one command chain.
How it works
:args **/*.pypopulates the argument list with all.pyfiles recursively in the current directory|chains Ex commands together:argdo %s/old/new/gcruns the substitution on every file in the arglist- The
cflag gives you interactive confirmation at each match (omit it to replace all silently) | updatesaves each file only if it was modified (safer than:wwhich writes unconditionally)
Example
Rename a function across your entire Python project:
:args **/*.py | argdo %s/\<calculate_total\>/compute_total/g | update
This finds every .py file, replaces all whole-word occurrences of calculate_total with compute_total, and saves each changed file.
For a narrower scope, use :vimgrep and the quickfix list instead:
:vimgrep /calculate_total/ **/*.py
:cfdo %s/calculate_total/compute_total/gc | update
:cfdo only operates on files that actually contain matches, which is faster when you have many files but few matches.
Tips
- Use
:argswithout arguments to see the current argument list - Add
eto the substitute flags (/gce) to suppress errors on files with no matches, preventing the command from aborting early - Use
\<and\>word boundaries to avoid partial matches::%s/\<old\>/new/g :argdoand:cfdoboth support any Ex command, not just:s— you can run:norm,:g, or:deleteacross files too- Always version-control your project before running bulk replacements so you can review the diff and revert if needed
- Use
:bufdo %s/old/new/ge | updateif the files are already open as buffers - The quickfix approach (
:vimgrep+:cfdo) is generally preferred because it only touches files with actual matches