search and replace in files

I always try to do more things from within emacs; as said before, it's all about return on investment for all the time spent mastering emacs. One of the things I sometimes still used a separate terminal for, was search and replace of text in files – a quick grep or sed from the command line.

source code: tags

If you're searching (not replacing) symbols in source code, the most convenient way is to use a tagfile. A while ago, I discussed navigating through source code using tags (using GNU Global). I still often see people writing code in emacs, and then opening a terminal open to grep for function names, definition and so on… There is really no need for that; see the linked entry.

The GNU Global tagfile system unfortunately does not have a search-and-replace function; if you are using etags, however, you can use M-x tags-query-replace to replace the symbols in all files in the tagfile.

GNU Global users can of course still use the other search-and-replace mechanisms below.

other files: rgrep

For searching recursively for some strings (regexps) in a file tree, there is the very useful M-x rgrep. It will ask what you want to search for, some pattern for the files to match, and the top of the directory to search.

So, for example, if you want to find all occurences of the string 'FIXME' in txt-files in your ~/Documents directory tree, you would do something like:

M-x rgrep

and you will get at buffer with all matches. For this, it uses the same kind of buffer you use for compilation, and you can jump to the matching locations just like you can jump to error locations in the compilation output - that is why with M-x next-error you can jump from match to match, even though of course they are not really errors. The default shortcut for next-error is C-x`, but you can of course remap that to something saner, exempli gratia:

(global-set-key (kbd "<M-prior>") 'previous-error) 
(global-set-key (kbd "<M-next>")  'next-error)

so you can use M- with PgUp/PgDown to jump through the matches (or errors).

One final comment about rgrep: in the above example, the FIXME is a regular expression (as discussed in building regular expressions, while the *.txt is a shell pattern (see e.g. the findutils manual).

replacing: dired

If you want to replace text in multiple files, your best bet is to use dired, the emacs file manager.

dired deserves its own entry (and probably more than one), but if we just look at search-replace, the steps are not too hard. Suppose we want to replace all strings FOO with BAR in a bunch of files in ~/myfiles/. We open dired with C-x d, enter ~/myfiles, and a list of the files in that directory appears.

Now, we mark the files we'd like to change by moving the cursor to them and press m (unmark with u). You can also mark filenames matching some regexp with M-x dired-mark-files-regexp (or '* %', obviously) or files containing some regexp with M-x dired-mark-files-containing-regexp (or '* g').

After marking some files, you can use M-x dired-do-query-replace-regexp to interactively replace some regular expression in all of them – you have to press 'y' to confirm the changes. You can quit this process by pressing 'q'.

dired can even work recursively, as an anonymous commenter remarked (thanks!); slightly edited: You can type 'i' to insert a subdirectory to the dired buffer. You can also run M-x find-name-dired or M-x find-dired to generate a dired buffer with the results of the find-command. Then you can mark wanted files and perform query replace with 'Q'. Also see: Emacs Nerdery: Search & replace across files.

As an alternative, you can use an external package like FindR for recursive search & replace. I haven't used that one myself though, as I haven't had the need yet.


Anonymous said...

"Dired does not work recursively - it's one directory at a time."

Hmm, not entirely true in practice. You can type "i" to insert subdirectory to the dired buffer. You can also run "M-x find-name-dired" or "M-x find-dired" to generate a dired buffer with find command. Then you can mark wanted files and perform query replace (Q).

Check blog post Search & replace across files.

djcb said...

@Anonymous: thanks! updated.

Ferk said...

In dired you can also do...

M-x wdired-change-to-wdired-mode

(long to type, but you can bind some key to it)

And then it will make the dired buffer editable. You can then search-replace filenames or do any editing the usual way you would edit lines in a text buffer, and then apply changes with C-x C-s.

Ferk said...

ah, sorry, my bad. wdired is for renaming files, your nice tip is for replacing expressions in multiple files.

Nice tips

piyo said...

One thing I don't like about rgrep and friends is that emacs modifies your search regexp before passing it to grep. I disable this by changing the appropriate grep-expand-keywords.

;; modify grep-expand-keywords so that regexp is passed unmodified
(let ((assocR (assoc "<R>" grep-expand-keywords)))
;; was originally ("<R>" shell-quote-argument (or regexp ""))
(when assocR
(setcdr assocR (list 'concat "'" 'regexp "'"))))

Perhaps it's because I use extended-regexp mode in grep instead:

(setq grep-find-template
;; was originally "find . <X> -type f <F> -print0 | xargs -0 -e grep <C> -nH -e <R>"
"find . <X> -type f \\( ! -name '*~' \\) -a -type f <F> -print0 | xargs -0 -e grep <C> --extended-regexp -nH -e <R>")

Michael said...

You can also open a dired including all subfolders recursively by passing a numerical argument to dired:

M-1 M-x dired

(thats M-'one', not M-'ell').

You then get prompted for the flags you would like to pass to dired (nothing more than the arguments you would pass to 'ls' under Unix). Hence for a recursive list, add -R to the list of options.

Drew said...

With Icicles you can easily search (and replace on demand) through multiple files. You can choose the files interactively (using multiple-pattern matching if you want) or take them from a file or fileset.

With Dired+ and Icicles you can search (and replace on demand) through the marked files, including those in marked subdirectories, recursively.