When using sed to replace strings in-place, is there a way to make it report the changes it does (without relying on a diff of old and new files)?
For instance, how can I change the command line
find . -type f | xargs sed -i 's/abc/def/g'
so I can see the changes that are made on the fly?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
You could use sed‘s w flag with either /dev/stderr, /dev/tty, /dev/fd/2 if supported on your system. E.g. with an input file like:
foo first second: missing third: foo none here
running
sed -i '/foo/{
s//bar/g
w /dev/stdout
}' file
outputs:
bar first third: bar
though file content was changed to:
bar first second: missing third: bar none here
So in your case, running:
find . -type f -printf 'n%p:n' -exec sed -i '/foo/{
s//bar/g
w /dev/fd/2
}' {} ;
will edit the files in-place and output:
./file1: bar stuff more bar ./file2: ./file3: bar first third: bar
You could also print something like original line >>> modified line e.g.:
find . -type f -printf 'n%p:n' -exec sed -i '/foo/{
h
s//bar/g
H
x
s/n/ >>> /
w /dev/fd/2
x
}' {} ;
edits the files in-place and outputs:
./file1: foo stuff >>> bar stuff more foo >>> more bar ./file2: ./file3: foo first >>> bar first third: foo >>> third: bar
Method 2
You could do it in two passes using the print action on the first pass with:
find . -type f | xargs sed --quiet 's/abc/def/gp'
where --quiet makes sed not show every line and the p suffix shows only lines where the substitution has matched.
This has the limitation that sed will not show which files are being changed which of course could be fixed with some additional complexity.
Method 3
I don’t think that’s possible, but a workaround might be to use perl instead:
find . -type f | xargs perl -i -ne 's/abc/def/ && print STDERR'
This will print the altered lines to standard error. For example:
$ cat foo fooabcbar $ find . -type f | xargs perl -i -ne 's/abc/def/ && print STDERR' foodefbar
You can also make this slightly more complex, printing the line number, file name, original line and changed line:
$ find . -type f |
xargs perl -i -ne '$was=$_; chomp($was);
s/abc/def/ && print STDERR "$ARGV($.): $was : $_"'
./foo(1): fooabcbar : foodefbar
Method 4
It is possible using the w flag which writes the current pattern to a file. So by adding it to the substitute command we can report successive substitutions to a file and print it after the job is done.
I also like to colorize the replaced string with grep.
sed -i -e "s/From/To/gw /tmp/sed.done" file_name grep --color -e "To" /tmp/sed.done
Note, that there must be only one space between the w and its file name.
This is even better than diff, since diffing might also show changes even if they were not made by sed.
Method 5
I like the @terdon solution – perl is good for this.
Here’s my tweaked version that:
- won’t try to alter files that dont have have the matching string
- will backup the before version of the files that change (create a .bak version)
- will list every file/lineno changed, and
show the OLD and NEW versions of that line indented and underneath
for easy reading
code
find /tmp/test -type f ! -name "*.bak" -exec grep -l '/opt/gridmon' {} ; | xargs -L1 perl -ni'.bak' -e'$old=$_; s//opt/gridmon/~/g && print STDERR "$ARGV($.):ntOLD:$oldtNEW:$_"'
example output
/tmp/test/test4.cfg(13):
OLD: ENVFILE /opt/gridmon/server/etc/gridmonserver.cfg
NEW: ENVFILE ~/server/etc/gridmonserver.cfg
/tmp/test/test4.cfg(24):
OLD: ENVFILE /opt/gridmon/server/etc/gridmonserver.cfg
NEW: ENVFILE ~/server/etc/gridmonserver.cfg
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0