53

How do I replace every occurrence of a string with another string below my current directory?

Example: I want to replace every occurrence of www.fubar.com with www.fubar.ftw.com in every file under my current directory.

From research so far I have come up with

sed -i 's/www.fubar.com/www.fubar.ftw.com/g' *.php
rubo77
  • 19,527
  • 31
  • 134
  • 226
KRB
  • 4,875
  • 17
  • 42
  • 54
  • possible duplicate of [Awk/Sed: How to do a recursive find/replace of a string?](http://stackoverflow.com/questions/1583219/awk-sed-how-to-do-a-recursive-find-replace-of-a-string) – user2284570 Aug 04 '14 at 14:53

6 Answers6

97

You're on the right track, use find to locate the files, then sed to edit them, for example:

find . -name '*.php' -exec sed -i -e 's/www.fubar.com/www.fubar.ftw.com/g' {} \;

Notes

  • The . means current directory - i.e. in this case, search in and below the current directory.
  • For some versions of sed you need to specify an extension for the -i option, which is used for backup files.
  • The -exec option is followed by the command to be applied to the files found, and is terminated by a semicolon, which must be escaped, otherwise the shell consumes it before it is passed to find.
plaisthos
  • 6,255
  • 6
  • 35
  • 63
martin clayton
  • 76,436
  • 32
  • 213
  • 198
  • 5
    I strongly suggest to use `-i.bak` in sed. Anyhow, this is great solution. +1 – Michał Šrajer Sep 16 '11 at 22:26
  • @Michal what is the .bak for? Anyone, what exactly does the {} \ mean at the end of your statement? – KRB Sep 22 '11 at 13:59
  • @martin How do I escape single quotes and double quotes in the sed statement? – KRB Sep 22 '11 at 15:35
  • 1
    @KRB: .bak is for backup in case you change something you didn't want to change. `{}` is substituted by find command with file name when executing a command by `-exec`. – Michał Šrajer Sep 22 '11 at 21:47
  • @KRB: The `\;` is to pass `;` to the find command (without '\' bash will interpret it and not pass it as an argument). find expects `;` somewhere after '-exec' to know which arguments are for `find` it self and which are for called command. – Michał Šrajer Sep 22 '11 at 21:49
  • 1
    @KRB: in regex's the letters after last slash are options. Here '/g' stands for "do global" substitutions (all in a line, not only first). – Michał Šrajer Sep 22 '11 at 21:51
  • @KRB - you should be able to escape quotes in the usual shell way. It gets tricky when you have a mixture of single and double quotes. But you may want yo use double quotes around the "s///". Then you can escape single \' and double \" quotes within that sed command. – martin clayton Sep 22 '11 at 21:51
  • On OSX, sed requires an explicit argument for the in-place suffix, so you need to do `sed -i '' 's/...` – jake Mar 18 '15 at 19:29
  • 1
    @RockyMM - it's a placeholder for the "found file", see http://stackoverflow.com/questions/5607542/why-does-find-exec-mv-target-not-work-on-cygwin – martin clayton May 26 '15 at 16:35
  • If you add an -e before the 's/www' it also becomes compatible with non GNU sed (e.g. OS X) – plaisthos Jun 19 '18 at 17:15
3

Solution using find, args and sed:

find . -name '*.php' -print0 | xargs -0 sed -i 's/www.fubar.com/www.fubar.ftw.com/g'
johnny
  • 657
  • 7
  • 18
  • What's the difference between this and the selected answer @johnny – KRB Sep 22 '11 at 14:31
  • 1
    It uses one process more, but represents a more general pattern which is very useful in its own right; i.e. generate-args | xargs command – tripleee Sep 23 '11 at 05:26
  • xargs builds a command line and tries to run the command after as few times as possible, exec runs the command once for every file. -print0 and -0 takes care of filenames with spaces in them. – johnny Sep 23 '11 at 05:33
  • I think this is a good solution when running in a Windows environment where file names and folder names may have space characters in them. I find myself constantly having to use the `-print0 | xargs -r0` arg combo. – idclaar Nov 17 '14 at 19:11
  • The suggested solution worked great. Except for one gotcha: It replaces symlinks with a file of the target's contents. – Jonas Aug 23 '18 at 15:34
2

A pure bash solution

#!/bin/bash
shopt -s nullglob
for file in *.php
do
    while read -r line
    do
       echo "${line/www.fubar.com/www.fubar.ftw.com}"
    done < "$file" > tempo && mv tempo "$file"

done
bash-o-logist
  • 6,665
  • 1
  • 17
  • 14
1

A more efficient * alternative to the currently accepted solution:

`grep "www.fubar.com" . -lr | xargs sed -i 's/www.fubar.com/www.fubar.ftw.com/g'

This avoids the inefficiency of the find . -exec method, which needlessly runs a sed in-place replacement over all files below your current directory regardless of if they contain the string you're looking for or not, by instead using grep -lr. This gets just the files containing the string you want to replace which you can then pipe to xargs sed -i to perform the in-place replacement on just those files.


* : I used time to make a cursory comparison of my method with the accepted solution (adapted for my own use case); The find . -exec-style method took 3.624s to run on my machine and my above proposed solution took 0.156s, so roughly 23x faster for my use case.

Jimbobur
  • 13
  • 6
0

If there are no subfolders, a simpler to remember way is

replace "www.fubar.com" "www.fubar.ftw.com" -- *

where * can also be a list of files

from the manual:

Invoke replace in one of the following ways:

       shell> replace from to [from to] ... -- file_name [file_name] ...
       shell> replace from to [from to] ... < file_name

If you have hidden files with a dot you can add those to * with

shopt -s dotglob

If you only have one depth of subfolders you can use */* instead of *

replace "www.fubar.com" "www.fubar.ftw.com" -- */*
rubo77
  • 19,527
  • 31
  • 134
  • 226
0

When using ZSH as your shell you can do:

sed -i 's/www.fubar.com/www.fubar.ftw.com/g' **/*.php
Seweryn Niemiec
  • 1,123
  • 2
  • 13
  • 19