Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I want to search and replace some text in a large set of files excluding some instances. For each line, I want a prompt asking me if I need to replace that line or not. Something similar to vim's :%s/from/to/gc (with the c to prompt for confirmation), but across a set of folders. Is there some good command line tool or script that can be used?

share|improve this question
    
On the importance of proper formatting: I'd initially read your command as s/from/to/g with a formatting glitch after it, rather than s/from/to/gc with emphasis on the c as you'd attempted to write (you can't do that with Markdown, you could do it with <code> and <strong> HTML tags). –  Gilles May 3 '11 at 14:39

2 Answers 2

up vote 11 down vote accepted

Why not use vim?

Open all files in vim

vim $(find . -type f)

Or open only relevant files (as suggested by Caleb)

vim $(grep 'from' . -Rl)

And do then run the replace in all buffers

:bufdo %s/from/to/gc | update

You can also do it with sed, but my sed knowledge is limited.

share|improve this answer
    
Thanks, your answer made me do a late double-take: I realized I'd completely missed the interactive bit. I don't think that's even possible with sed (not enough input/output channels). –  Gilles May 3 '11 at 14:37
1  
You might speed this up by not opening ALL files in the current buffer by using grep instead of find so that you only open files that have known matches. vim $(grep 'from' . -Rl) –  Caleb May 3 '11 at 17:15
    
Thanks.The c ( astreriks around c) is needed ? or its a formatting problem ? –  balki May 5 '11 at 8:27
    
@balki that's a "formatting" problem. Fixed it –  Gert May 5 '11 at 11:04

You can do something crude with a small Perl script which is instructed to perform replacements line by line (-l -pe) in place on the files passed as arguments (-i):

perl -i -l -pe '
    if (/from/) {                            # is the source text present on this line?
        printf STDERR ("%s: %s [y/N]? ", $ARGV, $_);  # display a prompt
        $r=<STDIN>;                                   # read user response
        if ($r =~ /^[Yy]/) {                          # if user entered Y:
            s/from/to/g;                              # replace all occurences on this line
    }' /path/to/files

Possible improvements would be to color parts of the prompt and support things like “replace all occurences in the current file”. Separately prompting for each occurrence on a line would be harder.

Second part, matching the files. if there aren't too many files involved and you're running zsh, you can match all the files in the current directory and its subdirectories recursively:

perl -i -l -pe '…' **/*(.)

If your shell is bash ≥4, you can run perl … **/*, but that will produce spurious error messages because sed will try (and fail) to run on directories. If you only want to perform the replacement in a set of files such as C files, you can restrict the matches (that works in either bash ≥4 or zsh):

perl -i -l -pe '…' **/*.[hc]

If you need finer control over which files you're replacing, or your shell doesn't have the recursive directory matching construct **, or if you have too many files and get a “command line too long” error, use find. For example, to perform a replacement in all files named *.h or *.c in the current directory and its subdirectories (on older systems, you may need to use \; instead of + at the end of the line (the + form is faster but not available everywhere).

find . -type f -name '*.[hc]' -exec perl -i -l -pe '…' {} +

That being said, I'd stick to an interactive editor if you need interaction. Gert has shown a way to to this in Vim, though it requires opening all the files that you want to search through, which may be a problem if there are a lot.

In Emacs, here's how you can do this:

  1. Gather the file names with M-x find-name-dired (specify a toplevel directory) or M-x find-dired (specify an arbitrary find command line).
  2. In the resulting dired buffer, press t to mark all files, then Q (dired-do-query-replace-regexp) to perform a replacement with prompting on the marked files.
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.