I have a input file "test.txt" as below -
hostname=abc.com hostname=xyz.com
db-host=abc.com db-host=xyz.com
In each line, the value before space is the old value which needs to be replaced by the new value after the space recursively in a folder named "test". I am able to do this using below shell script.
#!/bin/bash
IFS=$'\n'
for f in `cat test.txt`
do
OLD=$(echo $f| cut -d ' ' -f 1)
echo "Old = $OLD"
NEW=$(echo $f| cut -d ' ' -f 2)
echo "New = $NEW"
find test -type f | xargs sed -i.bak "s/$OLD/$NEW/g"
done
"sed" replaces the strings on the fly in 100s of files.
Is there a trick or an alternative way by which i can get a report of the files changed like absolute path of the file & the exact lines that got changed ?
PS - I understand that sed or stream editors doesn't support this functionality out of the box. I don't want to use versioning as it will be an overkill for this task.
From
man sed
:This can be used to create a backup file when replacing. You can then look for any backup files, which indicate which files were changed, and
diff
those with the originals. Once you're done inspecting the diff, simply remove the backup files.If you formulate your replacements as
sed
statements rather than a custom format you can go one further, and use either ased
shebang line or pass the file to-f/--file
to do all the replacements in one operation.There's several problems with your script, just replace it all with (using GNU awk instead of GNU sed for inplace editing):
You'll find that is orders of magnitude faster than what you were doing.
That still has the issue your existing script does of failing when the "test.txt" strings contain regexp or backreference metacharacters and modifying previously-modified strings and handling partial matches - if that's an issue let us know as it's easy to work around with awk (and extremely difficult with sed!).
To get whatever kind of report you want you just tweak the
{ for ... }
line to print them, e.g. to print a record of the changes to stderr:Let's start with a simple rewrite of your script, to make it a little bit more robust at handling a wider range of replacement values, but also faster:
So, we loop over pairs of whitespace-separated fields (
old
,new
) in lines fromtest.txt
and run a standardsed
in-place replace on all files found withfind
.Pretty similar to your script, but we properly read lines from
test.txt
(no word splitting, pathname/variable expansion, etc.), we use Bash builtins whenever possible (no need to call external tools likecat
,cut
,xargs
); and we escapesed
metacharacters inold
/new
values for proper use assed
's regexp and replacement expressions.Now let's add logging from sed:
The
sed
script above changes eachold
tonew
, but it also writesold --> new
line to/dev/stdout
(Bash-specific), which we in turn append tochange.log
file. The-printf
action infind
outputs a "header" line with file name, for each file processed.With this, your "change log" will look something like:
Just for completeness, a quick walk-through the
sed
script. We act only on lines containing theold
value. For each such line, we store it to hold space (h
), change it tonew
, append that new value to the hold space (joined with newline,H
) which now holdsold\nnew
. We swap hold with pattern space (x
), so we can runs
command that converts it toold --> new
. After writing that to thestdout
withw
, we move thenew
back from hold to pattern space, so it gets written (in-place) to the file processed.