Find and Replace string in all files recursive using grep and sed
Solution 1
As @Didier said, you can change your delimiter to something other than /
:
grep -rl $oldstring /path/to/folder | xargs sed -i s@$oldstring@$newstring@g
Solution 2
grep -rl $oldstring . | xargs sed -i "s/$oldstring/$newstring/g"
Solution 3
The GNU guys REALLY messed up when they introduced recursive file searching to grep. grep is for finding REs in files and printing the matching line (g/re/p remember?) NOT for finding files. There's a perfectly good tool with a very obvious name for FINDing files. Whatever happened to the UNIX mantra of do one thing and do it well?
Anyway, here's how you'd do what you want using the traditional UNIX approach (untested):
find /path/to/folder -type f -print |
while IFS= read -r file
do
awk -v old="$oldstring" -v new="$newstring" '
BEGIN{ rlength = length(old) }
rstart = index($0,old) { $0 = substr($0,rstart-1) new substr($0,rstart+rlength) }
{ print }
' "$file" > tmp &&
mv tmp "$file"
done
Not that by using awk/index() instead of sed and grep you avoid the need to escape all of the RE metacharacters that might appear in either your old or your new string plus figure out a character to use as your sed delimiter that can't appear in your old or new strings, and that you don't need to run grep since the replacement will only occur for files that do contain the string you want. Having said all of that, if you don't want the file timestamp to change if you don't modify the file, then just do a diff on tmp and the original file before doing the mv or throw in an fgrep -q before the awk.
Caveat: The above won't work for file names that contain newlines. If you have those then let us know and we can show you how to handle them.
Solution 4
sed
expression needs to be quoted
sed -i "s/$oldstring/$newstring/g"
jmazaredo
Updated on January 05, 2020Comments
-
jmazaredo over 4 years
I'm getting a
sed: -e expression #1, char 22: unterminated `s' command
is there a problem on my script? Also the "oldstring" has special characters
#!bin/bash oldstring='<script>"[oldscript]"</script>' newstring='<script>"[newscript]"</script>' grep -rl $oldstring /path/to/folder | xargs sed -i s/$oldstring/$newstring/g
-
David W. about 11 years+1 When someone talks about using
sed
withgrep
, there is an excellent chance they should be using justsed
or switch toawk
. By the way, if you used-print0
in your find, and (in BASH) using-d\0
should take care of file names with new lines in them. -
Ed Morton about 11 years@DavidW. Right. And when you start talking about escaping RE metacharacters so that sed or grep doesn't treat them as such, then you need to switch to awk/index() or fgrep so you can look for strings instead of REs.
-
Ed Morton about 11 yearsThe above would fail in SO many interesting ways given so many possible contents of oldstring or newstring.
-
toto_tico about 10 years@EdMorton, would you please put an example of how does it fail? I know you posted the traditional form of doing below, but just saying it can fail leave the question of how. Then I could understand why I should your solution. These solution just seems much more easy. Thanks
-
Ed Morton about 10 yearsTry the above with any shell globbing character or an @ in either string variable. The globbing chars can/will match file names in your directory, if not today then some day in future when you last expect it, and the @ will terminate your sed command. It'll also fail if oldstring contains spaces in various locations, not to mention if either string contains newline characters. The OP specifically said
the "oldstring" has special characters
so that's a big heads up that any of what I just described and more is likely to happen. -
Ed Morton about 9 years@josten the
-r
arg forgrep
is an alternative to usingfind
not to usingawk
. You can useawk
withfind+xargs
instead ofgrep+sed
and the result will usually be more concise and efficient. Usingawk
will certainly not cause you to create huge convoluted one-liners. -
thoni56 about 9 yearsAlso take care that in some environments, notably Darwin/MacOSX you need to give an explicit backup extension like 'sed -i .bak'.
-
Captainlonate over 6 yearsSo the complication with this, is files with spaces in their names won't be picked up. If you had "/files/File Two.txt", sed would try "/files/File" and "Two.txt".
-
Dev over 3 years@thoni56 "notably Darwin/MacOSX" Also known as BSD...