Find and Replace string in all files recursive using grep and sed

110,627

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"
Share:
110,627
jmazaredo
Author by

jmazaredo

Updated on January 05, 2020

Comments

  • jmazaredo
    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.
    David W. about 11 years
    +1 When someone talks about using sed with grep, there is an excellent chance they should be using just sed or switch to awk. 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
    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
    Ed Morton about 11 years
    The above would fail in SO many interesting ways given so many possible contents of oldstring or newstring.
  • toto_tico
    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
    Ed Morton about 10 years
    Try 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
    Ed Morton about 9 years
    @josten the -r arg for grep is an alternative to using find not to using awk. You can use awk with find+xargs instead of grep+sed and the result will usually be more concise and efficient. Using awk will certainly not cause you to create huge convoluted one-liners.
  • thoni56
    thoni56 about 9 years
    Also take care that in some environments, notably Darwin/MacOSX you need to give an explicit backup extension like 'sed -i .bak'.
  • Captainlonate
    Captainlonate over 6 years
    So 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
    Dev over 3 years
    @thoni56 "notably Darwin/MacOSX" Also known as BSD...