Search and Replace from terminal, but with confirmation
Solution 1
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.
Solution 2
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/' {} \;
Solution 3
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
Solution 4
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' {} \;
Related videos on Youtube
random_forest_fanatic
Updated on September 18, 2022Comments
-
random_forest_fanatic over 1 year
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.
-
random_forest_fanatic about 9 yearsThanks for the answer! Is there any way to use this approach to process multiple files at once? And, is there a way to run this so that it doesn't open up the file and require me to exit it each time?
-
random_forest_fanatic about 9 yearsThe '+wqa' command seems to work great, but I think it's closing the buffer and preventing additional files to be searched (after the first file with a match is processed). Thanks for the help!
-
muru about 9 years@random_forest_fanatic the search happens through all files for me.
-
random_forest_fanatic about 9 yearsYou're getting the command above to work for replacing strings across two files? Everything I try seems to quit once I've made one replacement...
-
random_forest_fanatic about 9 yearsThis seems to be the closest to what I'm looking for! But, I'd really like to be able to look at each replacement within each file, rather than having to accept/reject replacement for all matches in a file.
-
αғsнιη about 9 years@random_forest_fanatic Updated answer.
-
muru about 9 years@random_forest_fanatic Hmm. You could combine the
find
with thevim
way to get the best of both worlds. -
muru over 6 years@random_forest_fanatic rather too late now, but looks like I had some personal config which saved edits automatically. Without saving changes after the
:s
command,bufdo
can't switch buffers and errors out. I fixed the multi-file command so it should now work fine. -
random_forest_fanatic over 6 yearsI can't even remember what I needed this for, but this solution works. Thanks!
-
Hunaphu over 2 yearsPerfect. For people finding this answer: it opens a vim session for each file where the string is found and if you press y, the replacement is done, the file is written and you move on to next. Note!
find ./*.ext
is good if you want to avoid replacement in binary files.