简体   繁体   中英

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?

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:

LC_ALL=C 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.)

对于 mac,更类似的方法是:

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

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

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?

None of the above work on OSX.

Do the following:

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

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'

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 -shttps://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

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.

如果您使用的是 zsh 终端,则可以使用通配符魔法:

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

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/ {} +
find . -type f | xargs sed -i '' 's/string1/string2/g'

请参阅此处了解更多信息。

2021

What worked for me :

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

this worked for me on mac

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

however it was much faster with specifying the file types I needed to search/replace in

LC_ALL=C && LANG=C && find . -type f -name '*.html' | xargs sed -i '' 's/OLD_STRING/NEW_STRING/g'

and as I was search/replacing urls, had to put backslash ( \ ) before slashes

LC_ALL=C && LANG=C && find . -type f -name '*.html' | xargs sed -i '' 's/https:\/\/yahoo.com/https:\/\/google.com/g'

https://bitbucket.org/masonicboom/serp is a go utility (ie 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

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

OSX 上的命令应该和 Unix 下漂亮的 UI 完全一样。

可以只说 $PWD 而不是 "."

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM