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 andc
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' {} \;