Recursive search and replace in text files on Mac and Linux
LinuxMacosShellLinux 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):
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