Emacs Nerdery: Search & replace across files

Whenever I encounter a problem that’s painful to solve, I consider finding a better way to do it. This involves weighing how long I think it might take to find (or create) a better solution versus the amount of time it would take to suck it up and do it the hard way. Where this fails is with painful processes encountered infrequently; since they don’t happen often, it’s less worthwhile to invest the time to improve it.

Multi-file search and replace is a perfect example of this kind of infrequently painful process. After messing around with for loops and sed in the shell, I finally sucked it up and got familiar with the Emacs way of doing it. It’s a great example of the synergy of Emacs tools, since it combines familiar features in a new way.

Search and replace in a single file

The facility for one-file S&R is called query-replace, and it’s bound to M-% (plain strings) and C-M-% (regexes) by default. As you’d expect, it prompts for search and replacement text, then replaces it in your buffer. When using regular expressions, you have the full power of them, with the ability to capture sub-expressions and use them in your replacement.

Directory editing

Dired is the Emacs file management package, and it’s quite powerful. You can invoke it with C-x d, or by pointing find-file (C-x C-f) at a directory.

Dired lets you mark files with m, or files matching a regular expression with % m. Pressing Q runs query-replace over the contents of those files.

Dired only shows files in one directory, so this doesn’t work if you want to replace in files which span directories.

Find with dired

This can be mostly solved with find-dired. As the name implies, it runs find, putting the results in a dired buffer. Once you have that, you can use Q to S&R in the matched files.

This is good if you need to replace a string in every file under a subdirectory, or in every file whose name matches a pattern. Where it fails is when you need to replace a string in files which contain that string.

Grep

Emacs has excellent integration with grep, in several flavors:

As you’ve no doubt figured out, we’ll be using find-grep-dired for this task.

Putting it together

Now we have all the pieces, let’s put them together. As an example, let’s say that we renamed an exception and need to change the catch blocks in every .php file in package.

We start by running find-grep-dired:

M-x find-grep-dired RET catch (FooException RET

This gives us a buffer of files containing catch (FooException. It includes stuff in .svn; we only want .php files, so we can mark those:

% m .php$ RET

Now that they’re marked, run query-replace on them:

Q catch (FooException RET catch (BarException RET

At this point, Emacs will cycle through every match in every file and ask you to confirm the replacement. If you press !, it will replace the rest of the matches in the current file, and start prompting you for the next; Y will replace every match in every file with no further prompting. Then you’ll need to save the files:

C-x s

This saves every modified file at once, and we’re done.

2009/05/07

Discussion

[...] The original post is at: http://atomized.org/2009/05/emacs-nerdery-search-replace-across-files/ [...]

Search and Replace in multiple files « Da Zhang’s Blog
2009/05/07

Would you be willing to start a page on EmacsWiki called FindGrepDiredSearchAndReplace with what you’ve written?

This is the shell-fu you were looking for.

$ find -path ‘*/.svn’ -prune -o -type f -name ‘*.php’ -execdir grep -qFe ‘catch (FooException’ {} \; -execdir sed -i -e ’s/catch (FooException/catch (BarException/g’ {} \;

You’re right, better for Emacs to handle the syntax.

aaron
2009/05/07

creating a tags file and using tags-query-replace works well if you want the set of files to be persistent. It’s useful if you have a set of files where you need to make changes on a regular basis.

Brian Gough
2009/05/08

Do you know grep-edit.el?
It is located at EmacsWiki.

M-x install-elisp-from-emacswiki grep-edit.el

rubikitch
2009/05/08

[...] really liked this post from Ian Eure demonstrating how to do multi-file search and replace in emacs. I frequently see [...]

Interesting Emacs Links – 2009 Week 19 « A Curious Programmer
2009/05/10

@Brian, This can work in some cases, but definitely wasn’t what I needed. It runs query-replace on every file in the tagfile. I have upwards of 2000 in mine, and they’re all on the other end of a tramp connection. It’s better to use grep-find to locate the exact files I want to change.

@rubikitch, That looks interesting, but it seems that you have to perform the edit by hand for each file.

Ian
2009/05/10

find-grep-dired is mentioned in http://www.emacswiki.org/emacs/DiredFindInLisp — I’ve created http://www.emacswiki.org/emacs/FindGrepDiredSearchAndReplace , please hack as appropriate.

Thomas
2009/06/22

No, if its like moccur-edit you can just query-replace on the grep buffer and then apply changes.

hsuh
2009/06/22

Participate