Search and Replace from terminal, but with confirmation

In many text editors, you can do a search and replace and be given the option to inspect each found case. I'd like to be able to do something similar to this at the command line in Ubuntu. I know sed offers the ability to find and replace strings across multiple files, but is there anyway to have each replacement confirmed by the user?

Ideally, I'd like a solution that would allow me to do this "prompted" find and replace across all files within a directory.


How about Vim?

vim '+%s/set/bar/gc' some_file
  • + is used to run a command after the file has loaded.
  • % runs the command over the whole buffer (file).
  • gc are flags to :substitute, g for acting over all expressions in the line and c for confirming each substitution.

You can't actually prevent Vim from opening up the file, but you can automate saving and exiting:

vim '+bufdo %s/set/bar/gc | up' '+q' some_file another_file
  • bufdo runs the command over each buffer. This includes the part after |, so changes to each buffer is saved (up).
  • q quits, which exits Vim since we are now at the last buffer.

You can use combination of find and its -ok command. This command is the same as the -exec command but ask the user first before execution of each specified command. If the user agrees, run the command. Otherwise just return false.

from man find:

-ok command ;
      Like  -exec  but ask the user first. If the user agrees, run the command. 
      Otherwise just return false. If the command is run, its standard input is 
      redirected from /dev/null.

So, you can use the command as follows:

$ find ./ -name filename -ok sed 's/foo/bar/' {} \;
< sed ... filename > ?

this will prompt the user as shown in the second line above.

If you enter y then the sed replacement command will be executed and your replacement will be made. If you enter n then the -ok command ignores the sed command.


If you want to do this "prompted" find and replace across all files within a directory, use the command as following:

$ find /path/to/directory -type f -ok sed 's/foo/bar/' {} \;

I would just write a little Perl script:

#!/usr/bin/env perl
use strict;
use warnings;

my $pat="$ARGV[0]";
my $replacement=$ARGV[1];
my $file="$ARGV[2]";

## Open the input file
open(my $fh, "$file");

## Read the file line by line
while (<$fh>) {
    ## If this line matches the pattern
    if (/$pat/) {
        ## Print the current line
        print STDERR "Line $. : $_";
        ## Prompt the user for an action
        print STDERR "Substitute $pat with $replacement [y,n]?\n";
        ## Read the user's answer
        my $response=<STDIN>;
        ## Remove trailing newline
        chomp($response);
        ## If the answer is y or Y, make the replacement.
        ## All other responses will be ignored. 
        if ($response eq 'Y' || $response eq 'y') {
            s/$pat/$replacement/g;
        }    
    }
    ## Print the current line. Note that this will 
    ## happen irrespective of whether a replacement occured.
    print;
}

Save the file as ~/bin/replace.pl, make it executable with chmod a+x ~/bin/replace.pl and run it with the pattern to match as the first argument, the replacement as the second and the file name as the third:

replace.pl foo bar file > newfile

To run it on multiple files, for example all *.txt files, wrap it in a bash loop:

for file in *txt; do 
    replace.pl foo bar "$file" > "$file".new
done

And to edit the file "in place":

for file in *txt; do 
    tmp=$(mktemp)
    replace.pl foo bar "$file" > "$tmp" && mv "$tmp" "$file"
done

You can combine muru's suggestion to use vim with αғsнιη's suggestion to use find (which has the advantage of recursing into subdirectories), for example:

find ./ -type f -exec vim -c '%s/originalstring/replacementstring/gc' -c 'wq' {} \;