906

How do I find and replace every occurrence of:

subdomainA.example.com

with

subdomainB.example.com

in every text file under the /home/www/ directory tree recursively?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tedd
  • 9,105
  • 3
  • 17
  • 5
  • 114
    Tip: Don't do the below in an svn checkout tree... it will overwrite magic .svn folder files. – J. Polfer Nov 08 '10 at 19:42
  • 9
    oh my god this is exactly what I just did. But it worked and doesn't seem to have done any harm. Whats the worst that could happen? – J. Katzwinkel Feb 06 '13 at 17:56
  • 5
    @J.Katzwinkel: at the very least, it may corrupt checksums, which may corrupt your repository. – ninjagecko May 14 '13 at 13:36
  • 4
    Quick tip for all the people using sed: It will add trailing newlines to your files. If you don't want them, first do a find-replace that won't match anything, and commit that to git. Then do the real one. Then rebase interactively and delete the first one. – funroll Oct 03 '14 at 19:09
  • @funroll Or use a tool which doesn't force the addition of newlines. such as Perl; or accept the fact that POSIX strictly requires text files to have line endings. – tripleee Feb 19 '16 at 05:44
  • 6
    You can exclude a directory, such as git, from the results by using `-path ./.git -prune -o` in `find . -path ./.git -prune -o -type f -name '*matchThisText*' -print0` before piping to xargs – devinbost Aug 24 '16 at 19:58
  • This answer on Unix StackExchange website is pretty neat too: https://unix.stackexchange.com/a/112024/354626 – Sylhare May 24 '19 at 14:27
  • Alternative solution from SuperUser [How can I do a recursive find and replace from the command line?](https://superuser.com/a/428494/774713) : `find . -type f \( -iname \*.ht* -o -iname \*.php \) -exec sed -i'' -e 's/findString/replString/g' {} +` (strings must be escaped, eg. dots like `\.`) Might ignore filenames with spaces(?) – ashleedawg Aug 02 '20 at 13:55

37 Answers37

984
find /home/www \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

-print0 tells find to print each of the results separated by a null character, rather than a new line. In the unlikely event that your directory has files with newlines in the names, this still lets xargs work on the correct filenames.

\( -type d -name .git -prune \) is an expression which completely skips over all directories named .git. You could easily expand it, if you use SVN or have other folders you want to preserve -- just match against more names. It's roughly equivalent to -not -path .git, but more efficient, because rather than checking every file in the directory, it skips it entirely. The -o after it is required because of how -prune actually works.

For more information, see man find.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
  • 3
    This worked for me, and my case was find/replacing IP address values. Question for the gallery, though: Why are the dots escaped for the first `subdomainA\.example\.com` value but not for the second `sudomainB.example.com` value? I executed it in the suggested format, and it seemed to do the job perfectly, but I'm curious why the escaping is only presented for the first string pattern. – elrobis Jul 05 '20 at 16:12
  • 2
    This script will stop without reaching the end with the error `Permission denied` if one of the files has immutable flag. Better to use `-exec sed -i ... {} \;` instead of pipe. – Rafis Ganeev Oct 27 '20 at 09:09
  • I often use `find . -type f -print0 | xargs -0 sed -i -e 's/\r$//'` to replace all CRLFs with LFs in files recursively in a specific directory. – KaiserKatze Dec 02 '20 at 15:53
  • 1
    using MACOS and frustrated why it is not working -> try -> `find . \( ! -regex '.*/\..*' \) -type f | LC_ALL=C xargs sed -i '' 's/foo/bar/g'` – Mushrankhan May 31 '21 at 15:14
  • 2
    @elrobis (12 years later, but for the record) the first URL used escaped dots because it was in the regex match text and is special, but the second URL was in the replacement text and dots are not special in that context. – SensorSmith Oct 30 '21 at 22:46
495

The simplest way for me is

grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'
Anatoly
  • 5,119
  • 1
  • 14
  • 8
  • 62
    This works especially well, when you need to exclude directories, like with `.svn`. For example: `grep -rl oldtext . --exclude-dir=.svn | xargs sed -i 's/oldtext/newtext/g'` – phyatt Nov 13 '15 at 21:36
  • 50
    On macOS, `sed -i` causes `sed: 1: "file_path": invalid command code .`. This is because -i is a different flag on macOS. I found `grep -rl old . | xargs sed -i "" -e 's/old/new/g'` works. I found [this](https://stackoverflow.com/questions/19456518/invalid-command-code-despite-escaping-periods-using-sed) useful – Ben Butterworth Oct 13 '20 at 14:07
  • 5
    If you are using a compiled language and want to avoid checking binaries, you can pass the I flag like `grep -Irl oldtext . | xargs sed -i 's/oldtext/newtext/g'` – TomDane Nov 25 '20 at 20:35
  • 2
    In a git project, be sure and use `git grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'` to avoid searching the dependencies (which are probably ignored via .gitignore) :) Great solution! @phyatt this is a better way to do that. – rjurney Apr 24 '21 at 19:20
  • 2
    using MACOS and frustrated why it is not working -> try -> `grep -rl 'SEARCHSTRING' ./ | LC_ALL=C xargs sed -i '' 's/SEARCHSTRING/REPLACESTRING/g' ` – Mushrankhan May 31 '21 at 15:15
  • this fails if the file names contain spaces `sed: can't read TOKEN: No such file or directory`, were token is one word separated by spaces – toto_tico Jul 25 '22 at 11:24
  • 5
    I found that you can add `-Z` to grep, and `-0` to xargs to capture filenames with spaces: `grep -rlZ oldtext . | xargs -0 sed -i 's/oldtext/newtext/g'`: https://stackoverflow.com/questions/17296525/grep-and-sed-with-spaces-in-filenames – toto_tico Jul 25 '22 at 11:38
  • another important aspect is that the above command will update the timestamp of only affected files – Aris Nov 01 '22 at 06:32
  • For me, the simplest macOS solution was `grep -rl oldtext . | xargs perl -pi -e 's/oldtext/newtext/g'` – rkechols Nov 18 '22 at 19:26
  • Won't work without `grep --color=no ...`, so, use `grep --color=no -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'` – pylover Jan 02 '23 at 18:00
  • The fix for file names with spaces is to change the xargs delimiter for newlines, as in `grep -rl oldtext . | xargs -d '\n' sed -i 's/oldtext/newtext/g'` – Gus Neves Jan 20 '23 at 05:59
  • Not sure if this is unique to VS Code's Git Bash terminal on Windows 10, but `grep -rl 'oldtext' * | xargs set -i 's/oldtext/newtext/g'` properly ignores hidden files/directories and captures files with spaces in their name. It's the `*` that's making it ignore hidden files (file name starts with .). – zecuse May 15 '23 at 17:37
  • Why don't you consider adding @BenButterworth 's [comment](https://stackoverflow.com/questions/1583219/how-can-i-do-a-recursive-find-replace-of-a-string-with-awk-or-sed?noredirect=1&lq=1#comment113766736_22385837) into your answer as a note for OS X users? Judging by it being highly upvoted, it would help a lot of additional viewers in future. – joeljpa May 19 '23 at 08:13
308

Note: Do not run this command on a folder including a git repo - changes to .git could corrupt your git index.

find /home/www/ -type f -exec \
    sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

Compared to other answers here, this is simpler than most and uses sed instead of perl, which is what the original question asked for.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 57
    Note that if you're using BSD sed (including on Mac OS X) you'll need to give an explicit empty string arg to sed's `-i` option. ie: `sed -i '' 's/original/replacement/g'` – Nathan Craike Mar 23 '12 at 01:29
  • How can I modify it to exclude .git subfolder? – reducing activity Mar 22 '21 at 15:48
  • @reducingactivity Hi! You can use this : `grep -rl placeholder . | grep -Ev ".git" | xargs sed -i s/placeholder/lol/g` (grep -Ev excludes patterns) - TIP: before actually running it to replace it, use it first without the `-i` like a dry-run. – Eos Antigen Feb 14 '22 at 06:37
104

All the tricks are almost the same, but I like this one:

find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +
  • find <mydir>: look up in the directory.

  • -type f:

    File is of type: regular file

  • -exec command {} +:

    This variant of the -exec action runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; the total number of invocations of the command will be much less than the number of matched files. The command line is built in much the same way that xargs builds its command lines. Only one instance of `{}' is allowed within the command. The command is executed in the starting directory.

I159
  • 29,741
  • 31
  • 97
  • 132
54

For me the easiest solution to remember is https://stackoverflow.com/a/2113224/565525, i.e.:

sed -i '' -e 's/subdomainA/subdomainB/g' $(find /home/www/ -type f)

NOTE: -i '' solves OSX problem sed: 1: "...": invalid command code .

NOTE: If there are too many files to process you'll get Argument list too long. The workaround - use find -exec or xargs solution described above.

Community
  • 1
  • 1
Robert Lujo
  • 15,383
  • 5
  • 56
  • 73
  • 1
    On Cygwin it produces `sed: can't read : No such file or directory`. Why and how to fix? – pmor Feb 10 '22 at 13:59
42
cd /home/www && find . -type f -print0 |
      xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • 1
    Some explanation would be in order, especially as it doesn't use any of the asked-for tools (the question is also tagged with them). E.g., what is the idea/gist? Please respond by editing your answer, not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Nov 01 '21 at 20:09
37

For anyone using silver searcher (ag)

ag SearchString -l0 | xargs -0 sed -i 's/SearchString/Replacement/g'

Since ag ignores git/hg/svn file/folders by default, this is safe to run inside a repository.

Jacob Wang
  • 4,411
  • 5
  • 29
  • 43
23

This one is compatible with git repositories, and a bit simpler:

Linux:

git grep -z -l 'original_text' | xargs -0 sed -i 's/original_text/new_text/g'

Mac:

git grep -z -l 'original_text' | xargs -0 sed -i '' -e 's/original_text/new_text/g'

(Thanks to http://blog.jasonmeridth.com/posts/use-git-grep-to-replace-strings-in-files-in-your-git-repository/)

n0099
  • 520
  • 1
  • 4
  • 12
seddonym
  • 16,304
  • 6
  • 66
  • 71
  • Wiser to use `git-grep`'s `-z` option together with `xargs -0`. – gniourf_gniourf Jun 24 '16 at 08:42
  • `git grep` obviously only makes sense in a `git` repo. The general replacement would be `grep -r`. – tripleee Jun 24 '16 at 08:59
  • @gniourf_gniourf Can you explain? – Petr Peller Jan 24 '17 at 10:56
  • 3
    @PetrPeller: with `-z`, `git-grep` will separate the output fields by null bytes instead of newlines; and with `-0`, `xargs` will read the input separated by null bytes, instead of blanks (and not do weird stuff with quotes). So if you don't want the command to break if the filenames contain spaces, quotes or other funny characters, the command is: `git grep -z -l 'original_text' | xargs -0 sed ...`. – gniourf_gniourf Jan 24 '17 at 12:25
19

To cut down on files to recursively sed through, you could grep for your string instance:

grep -rl <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

If you run man grep you'll notice you can also define an --exlude-dir="*.git" flag if you want to omit searching through .git directories, avoiding git index issues as others have politely pointed out.

Leading you to:

grep -rl --exclude-dir="*.git" <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g
domdambrogia
  • 2,054
  • 24
  • 31
19

A straight forward method if you need to exclude directories (--exclude-dir=..folder) and also might have file names with spaces (solved by using 0Byte for both grep -Z and xargs -0)

grep -rlZ oldtext . --exclude-dir=.folder | xargs -0 sed -i 's/oldtext/newtext/g'
inetphantom
  • 2,498
  • 4
  • 38
  • 61
18

An one nice oneliner as an extra. Using git grep.

git grep -lz 'subdomainA.example.com' | xargs -0 perl -i'' -pE "s/subdomainA.example.com/subdomainB.example.com/g"
mahemoff
  • 44,526
  • 36
  • 160
  • 222
Jimmy Kane
  • 16,223
  • 11
  • 86
  • 117
  • 3
    Good idea if working inside a git repo as you don't risk overwriting .git/ contents (as reported in the comments to another answer). – mahemoff Apr 06 '14 at 21:22
  • 1
    Thanks, I use it as a bash function `refactor() { echo "Replacing $1 by $2 in all files in this git repository." git grep -lz $1| xargs -0 perl -i'' -pE "s/$1/$2/g" }` Usage, for example to replace 'word' with 'sword': `refactor word sword` then verify what it did with `git diff`. – Paul Rougieux Dec 18 '19 at 16:14
18

Simplest way to replace (all files, directory, recursive)

find . -type f -not -path '*/\.*' -exec sed -i 's/foo/bar/g' {} +

Note: Sometimes you might need to ignore some hidden files i.e. .git, you can use above command.

If you want to include hidden files use,

find . -type f  -exec sed -i 's/foo/bar/g' {} +

In both case the string foo will be replaced with new string bar

Sazzad Hissain Khan
  • 37,929
  • 33
  • 189
  • 256
  • kind of slow, but worked for me me great! – Aris Oct 27 '22 at 07:12
  • another drawback is that this will update the timestamps of all found files, even if there is no text replacement. For my application this caused an issue, as we depend on timestamps. – Aris Nov 01 '22 at 06:33
14
find /home/www/ -type f -exec perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

find /home/www/ -type f will list all files in /home/www/ (and its subdirectories). The "-exec" flag tells find to run the following command on each file found.

perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

is the command run on the files (many at a time). The {} gets replaced by file names. The + at the end of the command tells find to build one command for many filenames.

Per the find man page: "The command line is built in much the same way that xargs builds its command lines."

Thus it's possible to achieve your goal (and handle filenames containing spaces) without using xargs -0, or -print0.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
9

I just needed this and was not happy with the speed of the available examples. So I came up with my own:

cd /var/www && ack-grep -l --print0 subdomainA.example.com | xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

Ack-grep is very efficient on finding relevant files. This command replaced ~145 000 files with a breeze whereas others took so long I couldn't wait until they finish.

Henno
  • 1,448
  • 4
  • 18
  • 30
9

or use the blazing fast GNU Parallel:

grep -rl oldtext . | parallel sed -i 's/oldtext/newtext/g' {}

Beware that if you run this in the root of a git repository, you may end up corrupting your git index. To avoid this, you can use ripgrep instead of grep, like so:

rg -l oldtext | parallel sed -i 's/oldtext/newtext/g' {}
Newbyte
  • 2,421
  • 5
  • 22
  • 45
microo8
  • 3,568
  • 5
  • 37
  • 67
  • how does one installs GNU Parallel? – eri0o May 26 '20 at 18:17
  • try to find the parallel package. arch: `sudo pacman -S parallel`; ubuntu/debian: `sudo apt-get install parallel`; fedora: `dnf install parallel`; I use arch btw – microo8 May 27 '20 at 06:12
7

grep -lr 'subdomainA.example.com' | while read file; do sed -i "s/subdomainA.example.com/subdomainB.example.com/g" "$file"; done

I guess most people don't know that they can pipe something into a "while read file" and it avoids those nasty -print0 args, while presevering spaces in filenames.

Further adding an echo before the sed allows you to see what files will change before actually doing it.

MadMan2064
  • 436
  • 3
  • 4
  • The reason `-print0` is useful is that it handles cases which `while read` simply cannot handle -- a newline is a valid character in a Unix file name, so for your code to be completely robust, it needs to cope with such file names, too. (Also, you want `read -r` to avoid some pesky POSIX legacy behavior in `read`.) – tripleee Feb 18 '16 at 06:30
  • Also, the `sed` is a no-op if there are no matches, so the `grep` isn't really necessary; though it is a useful optimization for avoiding to rewrite files which do not contain any matches, if you have lots of those, or want to avoid updating date stamps on files needlessly. – tripleee Feb 18 '16 at 06:31
7

According to this blog post:

find . -type f | xargs perl -pi -e 's/oldtext/newtext/g;'
J.Hpour
  • 971
  • 11
  • 20
  • How do you escape slashes `/` ?. For example, I want to replace IP addresses: `xxx.xxx.xxx.xxx` for `xxx.xxx.xxx.xxx/folder` – Pathros Mar 16 '18 at 20:09
  • You can escape the `/` with \ . For example : `find . -type f | xargs perl -pi -e 's/xxx.xxx.xxx.xxx\/folder/newtext/g;'` – J.Hpour Mar 17 '18 at 15:55
  • 1
    Perl, like `sed`, allows you to use any character as the delimiter after `s`; so, for example, try `s%foo/bar%baz/quux%` to replace `foo/bar` with `baz/quux` – tripleee Jul 26 '22 at 11:42
6

Try this:

sed -i 's/subdomainA/subdomainB/g' `grep -ril 'subdomainA' *`
Jason Plank
  • 2,336
  • 5
  • 31
  • 40
RikHic
  • 79
  • 1
  • 1
  • 1
    Hi @RikHic, nice tip - was thinking about something like this; unfortunately that formatting above didn't quite turn out right :) So I'll try with a pre tag (doesn't work) - so with escaping backticks then: `sed -i 's/subdomainA/subdomainB/g'` ` `grep -ril 'subdomainA' /home/www/*` ` - this still doesn't look all too good, but should survive copypaste :) Cheers! – sdaau Mar 05 '11 at 00:00
5
#!/usr/local/bin/bash -x

find * /home/www -type f | while read files
do

sedtest=$(sed -n '/^/,/$/p' "${files}" | sed -n '/subdomainA/p')

    if [ "${sedtest}" ]
    then
    sed s'/subdomainA/subdomainB/'g "${files}" > "${files}".tmp
    mv "${files}".tmp "${files}"
    fi

done
petrus4
  • 616
  • 4
  • 7
4

If you do not mind using vim together with grep or find tools, you could follow up the answer given by user Gert in this link --> How to do a text replacement in a big folder hierarchy?.

Here's the deal:

  • recursively grep for the string that you want to replace in a certain path, and take only the complete path of the matching file. (that would be the $(grep 'string' 'pathname' -Rl).

  • (optional) if you want to make a pre-backup of those files on centralized directory maybe you can use this also: cp -iv $(grep 'string' 'pathname' -Rl) 'centralized-directory-pathname'

  • after that you can edit/replace at will in vim following a scheme similar to the one provided on the link given:

    • :bufdo %s#string#replacement#gc | update
Community
  • 1
  • 1
mzcl-mn
  • 41
  • 3
4

You can use awk to solve this as below,

for file in `find /home/www -type f`
do
   awk '{gsub(/subdomainA.example.com/,"subdomainB.example.com"); print $0;}' $file > ./tempFile && mv ./tempFile $file;
done

hope this will help you !!!

sarath kumar
  • 360
  • 2
  • 15
  • Works on MacOs wihtout any problems! All `sed` based commands failed when binaries were included even with the osx specific settings. – Jankapunkt Mar 13 '18 at 15:13
  • Careful...this will blow up if any of the files `find` returns have a space in their names! It's much safer to use `while read`: https://stackoverflow.com/a/9612560/1938956 – Soren Bjornstad Feb 15 '19 at 20:53
  • this won't work for files whose names contain spaces or new lines – phuclv Feb 14 '22 at 07:07
4

For replace all occurrences in a git repository you can use:

git ls-files -z | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

See List files in local git repo? for other options to list all files in a repository. The -z options tells git to separate the file names with a zero byte, which assures that xargs (with the option -0) can separate filenames, even if they contain spaces or whatnot.

Perseids
  • 12,584
  • 5
  • 40
  • 64
3

If you wanted to use this without completely destroying your SVN repository, you can tell 'find' to ignore all hidden files by doing:

find . \( ! -regex '.*/\..*' \) -type f -print0 | xargs -0 sed -i 's/subdomainA.example.com/subdomainB.example.com/g'
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • The parentheses appear to be superfluous. This previously had a formatting error which made it unusable (the Markdown rendering would eat some characters from the regex). – tripleee Feb 19 '16 at 05:59
3

A bit old school but this worked on OS X.

There are few trickeries:

• Will only edit files with extension .sls under the current directory

. must be escaped to ensure sed does not evaluate them as "any character"

, is used as the sed delimiter instead of the usual /

Also note this is to edit a Jinja template to pass a variable in the path of an import (but this is off topic).

First, verify your sed command does what you want (this will only print the changes to stdout, it will not change the files):

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

Edit the sed command as needed, once you are ready to make changes:

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed -i '' 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

Note the -i '' in the sed command, I did not want to create a backup of the original files (as explained in In-place edits with sed on OS X or in Robert Lujo's comment in this page).

Happy seding folks!

Community
  • 1
  • 1
Raphvanns
  • 1,766
  • 19
  • 21
  • Using `$(find ...)` to drive a `for` loop could fail; see https://mywiki.wooledge.org/BashFAQ/020 and https://www.iki.fi/era/unix/award.html#backticks – tripleee Jul 26 '22 at 11:44
3

just to avoid to change also

  • NearlysubdomainA.example.com
  • subdomainA.example.comp.other

but still

  • subdomainA.example.com.IsIt.good

(maybe not good in the idea behind domain root)

find /home/www/ -type f -exec sed -i 's/\bsubdomainA\.example\.com\b/\1subdomainB.example.com\2/g' {} \;
NeronLeVelu
  • 9,908
  • 1
  • 23
  • 43
3

Here's a version that should be more general than most; it doesn't require find (using du instead), for instance. It does require xargs, which are only found in some versions of Plan 9 (like 9front).

 du -a | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

If you want to add filters like file extensions use grep:

 du -a | grep "\.scala$" | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'
bbarker
  • 11,636
  • 9
  • 38
  • 62
2

to change multiple files (and saving a backup as *.bak):

perl -p -i -e "s/\|/x/g" *

will take all files in directory and replace | with x called a “Perl pie” (easy as a pie)

pevik
  • 4,523
  • 3
  • 33
  • 44
Stenemo
  • 611
  • 7
  • 13
  • Not recursive through directories though. – PKHunter Aug 24 '15 at 13:04
  • it is possible to pipe to it, which makes it very adjustable, including through directories. https://josephscott.org/archives/2005/08/perl-oneliner-recursive-search-and-replace/ and http://unix.stackexchange.com/questions/101415/how-to-use-wc-and-piping-to-find-how-many-files-and-directories-are-in-a-certain – Stenemo Aug 31 '15 at 16:57
2

For Qshell (qsh) on IBMi, not bash as tagged by OP.

Limitations of qsh commands:

  • find does not have the -print0 option
  • xargs does not have -0 option
  • sed does not have -i option

Thus the solution in qsh:

    PATH='your/path/here'
    SEARCH=\'subdomainA.example.com\'
    REPLACE=\'subdomainB.example.com\'

    for file in $( find ${PATH} -P -type f ); do

            TEMP_FILE=${file}.${RANDOM}.temp_file

            if [ ! -e ${TEMP_FILE} ]; then
                    touch -C 819 ${TEMP_FILE}

                    sed -e 's/'$SEARCH'/'$REPLACE'/g' \
                    < ${file} > ${TEMP_FILE}

                    mv ${TEMP_FILE} ${file}
            fi
    done

Caveats:

  • Solution excludes error handling
  • Not Bash as tagged by OP
Christoff Erasmus
  • 925
  • 1
  • 10
  • 26
2

Using combination of grep and sed

for pp in $(grep -Rl looking_for_string)
do
    sed -i 's/looking_for_string/something_other/g' "${pp}"
done
Pawel
  • 407
  • 1
  • 6
  • 14
  • @tripleee I modified this a bit. In this case output for command `grep -Rl pattern` generated list of files where the pattern is. Files are not read in `for` loop. – Pawel Feb 18 '16 at 22:04
  • Huh? You still have a `for` loop; if any returned file name contains whitespace, it will not work correctly, because the shell tokenizes the `for` argument list. But then you use the file name variable without quotes inside the loop, so it would break there instead if you fixed this. Correcting these remaining bugs would make yours identical to @MadMan2064's answer. – tripleee Feb 19 '16 at 05:45
  • @tripleee yes, that is true, I missed this. – Pawel Feb 19 '16 at 17:03
  • this won't work for files whose names contain spaces or new lines – phuclv Feb 14 '22 at 07:05
2
perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`
Sheena
  • 15,590
  • 14
  • 75
  • 113
2

I just use tops:

find . -name '*.[c|cc|cp|cpp|m|mm|h]' -print0 |  xargs -0 tops -verbose  replace "verify_noerr(<b args>)" with "__Verify_noErr(<args>)" \
replace "check(<b args>)" with "__Check(<args>)" 
tgunr
  • 1,540
  • 17
  • 31
  • plus one for ` '*.[c|cc|cp|cpp|m|mm|h]' ` – FractalSpace Apr 10 '18 at 19:35
  • That's a bug actually; it searches for files containing a single `c` or `|` or `p` or `m` or `h` after the dot. The square brackets create a character class which matches a single character out of the ones between the brackets; repeating a character is allowed but useless. – tripleee Jul 26 '22 at 11:40
2

Replacing find(1) with the simpler fd(1)/fdfind = https://github.com/sharkdp/fd:

fd . --type f --exec sed -i "s/original_string/new_string/g"

Addressing fd(1) iconsistent pkg & cmd names

  • on macOS homebrew: pkg and cmd = fd
  • on Ubuntu 20.04: pkg = fd-find, cmd = fdfind

I make an alias fdfind='fd' on macOS for consistent cmd naming (between my macOS and Linux platforms).

More on this point at https://github.com/sharkdp/fd/issues/1009.

More details and additional features

# bash examples:

1='original_string'
2='new______string'

# for this (the original-poster's) question:
1='subdomainA.example.com'
2='subdomainB.example.com'

# 'fdfind' (on at least Ubuntu 20.04) = 'fd' = https://github.com/sharkdp/fd

fdfind . --type f --exec sed -i "s/$1/$2/g"

# Here's a slightly-more-complex example that
# a. excludes (-E) .git/ and archive/ dirs, and
# b. performs a word-boundary search on the original_string (\<$1\>):
fdfind . -E .git/ -E archive/ --type f --exec sed -i "s/\<$1\>/$2/g"

Even fancier: controlling the word-boundary-ness from the third ($3) command-line paramter (third parameter = noword means no boundary, leftword means only left-side word boundary, rightword means only right-side boundary):

#!/usr/bin/env bash

#
# replace-tree.bash
#

# 'fdfind/fd-find' (on at least Ubuntu 20.04) = 'fd' = https://github.com/sharkdp/fd

if [ $# -lt 2 ]; then
  echo "usage: $0 <string_to_replace> <replacement_string> [noword|leftword|rightword]"
  exit 1
fi

original="\<$1\>"

if   [ "$3" = "noword" ];    then
  original="$1"
elif [ "$3" = "leftword" ];  then
  original="\<$1"
elif [ "$3" = "rightword" ]; then
  original="$1\>"
fi

fd . --type f --exec sed -i "s/$original/$2/g"

Example usage:

$ replace-tree.bash original_string new_string leftword
$
Johnny Utahh
  • 2,389
  • 3
  • 25
  • 41
1

This is the best all around solution I've found for OSX and Windows (msys2). Should work with anything that can get the gnu version of sed. Skips the .git directories so it won't corrupt your checksums.

On mac, just install coreutils first and ensure gsed is in the path -

brew install coreutils

Then I stick this function in my zshrc/bashrc ->

replace-recursive() {
    hash gsed 2>/dev/null && local SED_CMD="gsed" || SED_CMD="sed"
    find . -type f -name "*.*" -not -path "*/.git/*" -print0 | xargs -0 $SED_CMD -i "s/$1/$2/g"
}

usage: replace-recursive <find> <replace>
cchamberlain
  • 17,444
  • 7
  • 59
  • 72
1

To replace all content matching string_1 with string_2 of all .c and .h files in the current directory and subdirectories (excluding .git/).

This works on Mac:

find . -type f -path "*.git*" -prune -o -name '*\.[ch]' -exec \
sed -i '' -e 's/'$1'/'$2'/g' {} +

This should work on Linux (Have not tested yet):

find . -type f -path "*.git*" -prune -o -name '*\.[ch]' -exec \
sed -i 's/string_1/string_2/g' {} +
Klas. S
  • 650
  • 9
  • 21
1

If you have access to node you can do a npm install -g rexreplace and then

rexreplace 'subdomainA.example.com' 'subdomainB.example.com' /home/www/**/*.*
mathiasrw
  • 610
  • 4
  • 10
1

I'm surprised I've not seen the simple answer using file globbing, which I just used to scan/update ONLY packge.json files with **/package.json

this was macos specific under zsh

cd /home/www
sed -i '' -e 's/subdomainA.example.com/subdomainA.example.com/g' **/*
robert arles
  • 153
  • 2
  • 9
0

A simpler way is to use the below on the command line

find /home/www/ -type f|xargs perl -pi -e 's/subdomainA\.example\.com/subdomainB.example.com/g' 
Luke Chadwick
  • 1,648
  • 14
  • 24
Vijay
  • 65,327
  • 90
  • 227
  • 319