Recursive search and replace in text files on Mac and Linux

Recursive search and replace in text files on Mac and Linux



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?






Should probably moved to apple.stackexchange.com as it's not generic enough for linux nor all devs.

– AlikElzin-kilaka
Jul 14 '16 at 5:42


apple.stackexchange.com




13 Answers
13



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


less



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.


-i


-i ''



The following should do:



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


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



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


-type f


-exec


xargs


-print0


+


find


find






Works like a charm, thanks, and welcome to stackoverflow ;-)

– Jack
Mar 15 '12 at 7:33






My "this" in this substitution contains a forward slash (localhost/site) -- I am substituting parts of a URL in an .html file....how do I make such a substitution. I tried putting in double-quotes, but it fails.

– Angela
Sep 18 '13 at 4:00






Sed syntax allows using almost any character in place of the slash, e.g. you could use the % character: sed "s%localhost/site%blah/blah%". Another alternative is to backslash-escape the separator: sed "s/localhost/site/blah/blah/".

– TaylanUB
Sep 18 '13 at 11:06



%


sed "s%localhost/site%blah/blah%"


sed "s/localhost/site/blah/blah/"






Anybody else getting illegal byte sequence error? if so, try: LC_ALL=C find . -type f -name '*.txt' -exec sed -i '' s/this/that/ +, it worked for me.

– CodingCaio
May 31 '16 at 14:10


illegal byte sequence


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






This will be replacing only one ocurreny per file, use /g for multiple ocurrencies like LC_ALL=C find . -type f -exec sed -i '' s/search/replace/g +

– jamesjara
Aug 22 '17 at 6:20


/g


LC_ALL=C find . -type f -exec sed -i '' s/search/replace/g +



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


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






I wish I could upvote this every time I come back to it and use it. It'd be at +15 by now, easy.

– Droogans
Feb 26 '15 at 15:35






Agreed. This was the only one that worked for me.

– Andrew Faulkner
Aug 11 '16 at 9:00







For some reason, it does not work for me. It does nothing. I'm inside the folder form360 and I´m trying to change all string instances with the name easyform to form360, I´m running the following command: find . -name '*.php' -print0 | xargs -0 sed -i "" "s/easyform/form360/g"

– Andres Ramos
Jan 26 '17 at 21:23



find . -name '*.php' -print0 | xargs -0 sed -i "" "s/easyform/form360/g"






for me, this should be correct answer. It's the only one that worked for me.

– nosequeldeebee
Feb 1 '17 at 6:08






-print0 | xargs -0 does not work on my mac when file name contains space.

– user2807219
Apr 29 '17 at 10:49



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



On Mac OSX 10.11.5 this works fine:


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






xargs -I@ sed -i '' 's/old-word/new-word/g' @ worked for me after trying so many other non working suggestions. Thank you!

– BeC
Dec 28 '16 at 10:57



None of the above work on OSX.



Do the following:


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






how to scape '/' if SEARCH_FOR and REPLACE_WITH are paths?

– rraallvv
Mar 29 '13 at 16:35






Use a different delimiter. If you're using paths, a colon or pipe would work. 's|SEARCH|REPLACE|g', for example. Our use braces, as in 'sSEARCHREPLACE'.

– dannysauer
Sep 6 '13 at 1:04






dito question, trying it no on Mac -- but it seems to generate an error? For example, my path gets interpreted as a file? -bash: localhost/nohost: No such file or directory

– Angela
Sep 18 '13 at 4:02







this does not recurse through folders deep. Only one level.

– Sergey Romanov
Nov 27 '13 at 14:05






This works best!

– BadmintonCat
Apr 27 '17 at 7:44



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


-e


sed


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






I had to do the export from this answer + the line from the accepted answer (I didn't want backup files to be generated)

– Lance
Apr 29 '14 at 19:46






sed: RE error: illegal byte sequence

– FelikZ
Dec 28 '14 at 15:31






to adress the 'illegal byte sequence' error, try setting the LOCALE before running the command: export LC_CTYPE=C && export LANG=C

– cjoy
Jan 6 '16 at 1:24



export LC_CTYPE=C && export LANG=C






DO NOT EVER RUN this with '*' instead of '*.filetype' as I did if you are using Git. Or you can say goodbye to all your unpublished work.

– Sergey
May 26 '16 at 9:11







The mac version of the sed command requires an '' after the -i, so this answer is incorrect

– G Huxley
Jul 25 '18 at 22:08




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.



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



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



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



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


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


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



Refer here for more info.



The command on OSX should be exactly the same as it is Unix under the pretty UI.






I thought as much, but nope. It puts a ./ in front of the files when feeding to sed, so sed complains 'invalid command code'. Maybe I'm just tired ;-)

– Jack
Mar 14 '12 at 14:43







@JacobusR, please cut and paste from your OSX terminal -- you must be entering something slightly different.

– glenn jackman
Mar 14 '12 at 15:02






You may want -print0 added to the find flags and -0 to the xargs flags.

– jmtd
Mar 14 '12 at 16:46



-print0


find


-0


xargs






You can also just use -exec sed -i s/this/that/g + instead of -print and xargs

– Marian
Mar 14 '12 at 16:52



-exec sed -i s/this/that/g +


-print






@JacobusR - this works, even if there's a leading './' pathname. However, if there's an space in the filename, say "./jacobus R.txt", the use of xargs might see the space-delimited "./jacobus" as a file to pass to sed, and then with "R.txt", neither of which exist.

– Brett Hale
Mar 14 '12 at 17:02


xargs


sed



could just say $PWD instead of "."




Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).



Would you like to answer one of these unanswered questions instead?

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)