Recursive search and replace in text files on Mac and Linux

LinuxMacosShell

Linux Problem Overview


In the linux shell, the following command will recursively search and replace all instances of 'this' with 'that' (I don't have a Linux shell in front of me, but it should do).

find . -name "*.txt" -print | xargs sed -i 's/this/that/g'

What will a similar command on OSX look like?

Linux Solutions


Solution 1 - Linux

OS X uses a mix of BSD and GNU tools, so best always check the documentation (although I had it that less didn't even conform to the OS X manpage):

https://web.archive.org/web/20170808213955/https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/sed.1.html

sed takes the argument after -i as the extension for backups. Provide an empty string (-i '') for no backups.

The following should do:

find . -type f -name '*.txt' -exec sed -i '' s/this/that/g {} +

The -type f is just good practice; sed will complain if you give it a directory or so.

-exec is preferred over xargs; you needn't bother with -print0 or anything.

The {} + at the end means that find will append all results as arguments to one instance of the called command, instead of re-running it for each result. (One exception is when the maximal number of command-line arguments allowed by the OS is breached; in that case find will run more than one instance.)

If you get an error like "invalid byte sequence," it might help to force the standard locale by adding LC_ALL=C at the start of the command, like so:

LC_ALL=C find . -type f -name '*.txt' -exec sed -i '' s/this/that/g {} +

Solution 2 - Linux

For the mac, a more similar approach would be this:

find . -name '*.txt' -print0 | xargs -0 sed -i "" "s/form/forms/g"

Solution 3 - Linux

As an alternative solution, I'm using this one on Mac OSX 10.7.5

grep -ilr 'old-word' * | xargs -I@ sed -i '' 's/old-word/new-word/g' @

Credit goes to: Todd Cesere's answer

Solution 4 - Linux

None of the above work on OSX.

Do the following:

perl -pi -w -e 's/SEARCH_FOR/REPLACE_WITH/g;' *.txt

Solution 5 - Linux

A version that works on both Linux and Mac OS X (by adding the -e switch to sed):

export LC_CTYPE=C LANG=C
find . -name '*.txt' -print0 | xargs -0 sed -i -e 's/this/that/g'

Solution 6 - Linux

This is my workable one. on mac OS X 10.10.4

grep -e 'this' -rl . | xargs sed -i '' 's/this/that/g'

The above ones use find will change the files that do not contain the search text (add a new line at the file end), which is verbose.

Solution 7 - Linux

If you are using a zsh terminal you're able to use wildcard magic:

sed -i "" "s/search/high-replace/g" *.txt

Solution 8 - Linux

2021

What worked for me :

LC_ALL=C && LANG=C && find . -type f | xargs sed -i '' 's/old/new/g'

Solution 9 - Linux

Whenever I type this command I always seem to hose it up, or forget a flag. I created a Gist on github based off of TaylanUB's answer that does a global find replace from the current directory. This is Mac OSX specific.

https://gist.github.com/nateflink/9056302

It's nice because now I just pop open a terminal then copy in:

curl -s https://gist.github.com/nateflink/9056302/raw/findreplaceosx.sh | bash -s "find-a-url.com" "replace-a-url.com"

You can get some weird byte sequence errors, so here is the full code:

#!/bin/bash
#By Nate Flink
 
#Invoke on the terminal like this
#curl -s https://gist.github.com/nateflink/9056302/raw/findreplaceosx.sh | bash -s "find-a-url.com" "replace-a-url.com"
 
if [ -z "$1" ] || [ -z "$2" ]; then
  echo "Usage: ./$0 [find string] [replace string]"
  exit 1
fi
 
FIND=$1
REPLACE=$2
 
#needed for byte sequence error in ascii to utf conversion on OSX
export LC_CTYPE=C;
export LANG=C;
 
#sed -i "" is needed by the osx version of sed (instead of sed -i)
find . -type f -exec sed -i "" "s|${FIND}|${REPLACE}|g" {} +
exit 0

Solution 10 - Linux

I used this format - but...I found I had to run it three or more times to get it to actually change every instance which I found extremely strange. Running it once would change some in each file but not all. Running exactly the same string two-four times would catch all instances.

find . -type f -name '*.txt' -exec sed -i '' s/thistext/newtext/ {} +

Solution 11 - Linux

find . -type f | xargs sed -i '' 's/string1/string2/g'

Refer here for more info.

Solution 12 - Linux

For both macOS and Linux:

I'm not sure that the other answers address both Linux and macOS. If there are some that do, perhaps we can edit them to make that point clear.

Below is what I used in order to target both platforms.

Assume the file we want to perform the find/replace on contains the following text:

file.txt:

{{FROM}} hello world

Here is the script, replace.bash:

    #!/bin/bash
    PATTERN="s/{{FROM}}/HELLOWORLD/"
    if [[ `uname -s`" == "Darwin" ]]; then
        sed -i '' "$PATTERN" file.txt
        echo Darwin
    else
        sed -i "$PATTERN" file.txt
        echo Linuxxxx
    fi

I later discovered that using single brackets and a single equals cooperated better between sh and bash:

replace.sh:

    PATTERN="s/{{FROM}}/HELLOWORLD/"
    if [ `uname -s` = "Darwin" ]; then
        sed -i '' "$PATTERN" file.txt
        echo Darwin
    else
        sed -i "$PATTERN" file.txt
        echo Linuxxxx
    fi
Your own cross-platform sed:

I can see how someone might replace $PATTERN with $1 and file.txt with $2 and actually be able to create a wrapper around sed that works on both platforms, such as:

ssed:

    PATTERN="$1"
    FILE=$2
    if [ `uname -s` = "Darwin" ]; then
        sed -i '' "$PATTERN" $FILE
    else
        sed -i "$PATTERN" $FILE
    fi
$ chmod 755 ssed
$ ./ssed 's/{{FROM}}/jameswashere/' file

The file would then contain:

jameswashere hello world
What about recursive find/replace?

Now that we have our own platform-independent sed wrapper, we can use it, along with find, to loop through subdirectories and perform the find/replace on matching files:

$ find . -name "file" -exec ./ssed 's/{{FROM}}/that/g' {} \;

Solution 13 - Linux

https://bitbucket.org/masonicboom/serp is a go utility (i.e. cross-platform), tested on OSX, that does recursive search-and-replace for text in files within a given directory, and confirms each replacement. It's new, so might be buggy.

Usage looks like:

$ ls test
a  d  d2 z
$ cat test/z
hi
$ ./serp --root test --search hi --replace bye --pattern "*"                         
test/z: replace hi with bye? (y/[n]) y
$ cat test/z
bye

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJackView Question on Stackoverflow
Solution 1 - LinuxTaylanKammerView Answer on Stackoverflow
Solution 2 - LinuxGuybrush ThreepwoodView Answer on Stackoverflow
Solution 3 - LinuxMaciej GurbanView Answer on Stackoverflow
Solution 4 - Linuxeb80View Answer on Stackoverflow
Solution 5 - LinuxvrocView Answer on Stackoverflow
Solution 6 - LinuxNoteCodeView Answer on Stackoverflow
Solution 7 - LinuxMentorView Answer on Stackoverflow
Solution 8 - LinuxXysView Answer on Stackoverflow
Solution 9 - LinuxNate FlinkView Answer on Stackoverflow
Solution 10 - LinuxEmmaView Answer on Stackoverflow
Solution 11 - Linuxuser2725109View Answer on Stackoverflow
Solution 12 - Linuxjmort253View Answer on Stackoverflow
Solution 13 - Linuxuser4425237View Answer on Stackoverflow