Tedd
Tedd

Reputation: 9555

How can I do a recursive find/replace of a string with awk or sed?

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?

Upvotes: 951

Views: 822320

Answers (30)

domdambrogia
domdambrogia

Reputation: 2243

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

Upvotes: 20

Anatoly
Anatoly

Reputation: 5727

The simplest way for me is

grep -rlZ oldtext . | xargs -0 sed -i 's/oldtext/newtext/g'

Upvotes: 555

Vijay
Vijay

Reputation: 67291

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'

Upvotes: 1

bbarker
bbarker

Reputation: 13108

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'

Upvotes: 3

Johnny Utahh
Johnny Utahh

Reputation: 2478

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
$

Upvotes: 2

microo8
microo8

Reputation: 3784

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' {}

Upvotes: 13

seddonym
seddonym

Reputation: 17249

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/)

Upvotes: 27

robert arles
robert arles

Reputation: 193

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' **/*

Upvotes: 3

Nikita Fedyashev
Nikita Fedyashev

Reputation: 19048

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.

Upvotes: 1002

Employed Russian
Employed Russian

Reputation: 213879

cd /home/www && find . -type f -print0 |
      xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

Upvotes: 42

inetphantom
inetphantom

Reputation: 2594

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'

Upvotes: 20

Sazzad Hissain Khan
Sazzad Hissain Khan

Reputation: 40237

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

Upvotes: 18

tgunr
tgunr

Reputation: 1548

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>)" 

Upvotes: 2

J.Hpour
J.Hpour

Reputation: 969

According to this blog post:

find . -type f | xargs perl -pi -e 's/oldtext/newtext/g;'

Upvotes: 7

Stenemo
Stenemo

Reputation: 619

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)

Upvotes: 2

Sheena
Sheena

Reputation: 16242

perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`

Upvotes: 2

NeronLeVelu
NeronLeVelu

Reputation: 10039

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' {} \;

Upvotes: 3

Perseids
Perseids

Reputation: 13250

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.

Upvotes: 4

mathiasrw
mathiasrw

Reputation: 618

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/**/*.*

Upvotes: 1

Klas. S
Klas. S

Reputation: 692

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' {} +

Upvotes: 1

Raphvanns
Raphvanns

Reputation: 1894

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!

Upvotes: 3

Jacob Wang
Jacob Wang

Reputation: 4804

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.

Upvotes: 37

sarath kumar
sarath kumar

Reputation: 380

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 !!!

Upvotes: 5

John Zwinck
John Zwinck

Reputation: 249532

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.

Upvotes: 317

Pawel
Pawel

Reputation: 407

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

Upvotes: 2

Marcus Floyd
Marcus Floyd

Reputation: 49

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'

Upvotes: 3

unutbu
unutbu

Reputation: 880627

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.

Upvotes: 14

cchamberlain
cchamberlain

Reputation: 17966

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>

Upvotes: 1

MadMan2064
MadMan2064

Reputation: 436

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.

Upvotes: 7

mzcl-mn
mzcl-mn

Reputation: 41

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

Upvotes: 4

Related Questions