loading...
Cover image for Deceptively simple search-and-replace across multiple files

Deceptively simple search-and-replace across multiple files

victoria profile image Victoria Drake Originally published at victoria.dev on ・2 min read

While a multitude of methods exist to search for and replace words in a single file, what do you do when you’ve got a string to update across multiple unrelated files, all with different names? You harness the power of command line tools, of course!

First, you’ll need to find all the files you want to change. Stringing together what are effectively search queries for find is really only limited by your imagination. Here’s a simple example that finds Python files:

find . -name '*.py'
Enter fullscreen mode Exit fullscreen mode

The -name test searches for a pattern, such as all files ending in .py, but find can do a lot more with other test conditions, including -regex tests. Run find --help to see the multitude of options.

Further tune your search by using grep to get only the files that contain the string you want to change, such as by adding:

grep -le '\<a whale\>'
Enter fullscreen mode Exit fullscreen mode

The -l option gives you just the file names for all files containing a pattern (denoted with -e) that match “a whale”.

Using Vim’s impressive :bufdo lets you run the same command across multiple buffers, interactively working with all of these files without the tedium of opening, saving, and closing each file, one at a time.

Let’s plug your powerful find+grep results into Vim with:

vim `find . -name '*.py' \
-exec grep -le '\<a whale\>' {} \;`
Enter fullscreen mode Exit fullscreen mode

Using backtick-expansion to pass our search to Vim opens up multiple buffers ready to go. (Do :h backtick-expansion in Vim for more.) Now you can apply the Vim command :bufdo to all of these files and perform actions such as interactive search-and-replace:

:bufdo %s/a whale/a bowl of petunias/gce
Enter fullscreen mode Exit fullscreen mode

The g for “global” will change occurrences of the pattern on all lines. The e will omit errors if the pattern is not found. The c option makes this interactive; if you’re feeling confident, you can omit it to make the changes without reviewing each one.

When you’ve finished going through all the buffers, save all the work you’ve completed with:

:bufdo wq!
Enter fullscreen mode Exit fullscreen mode

Then bask in the glory of your saved time and effort.

Discussion

pic
Editor guide
Collapse
dmerejkowsky profile image
Dimitri Merejkowsky

I wrote a command-line tool with this purpose I think you would enjoy.

It's called ruplacer and here's how it works:

$ ruplacer old new src/
Patching src/a_dir/sub/foo.txt
-- old is everywhere, old is old
++ new is everywhere, new is new

Patching src/top.txt
-- old is nice
++ new is nice
Collapse
vlasales profile image
Vlastimil Pospichal
:vimgrep '\<a whale\>' **/*.py
:bufdo %s/a whale/a bowl of petunias/gce
:wq
Collapse
pyrsmk profile image
Aurélien Delogu

You should take a look at sd: github.com/chmln/sd

It's written Rust with a fairly simple API. But especially: it's performant!

Collapse
brandelune profile image
Jean-Christophe Helary

The difference with using sed for the job is the fact that you can do this interactively, right ?