在Mac和Linux上进行文本文件的递归搜索和替换

175
在Linux shell中,以下命令将递归搜索并替换所有实例中的“this”为“that”(我面前没有Linux shell,但应该可以这样做)。
find . -name "*.txt" -print | xargs sed -i 's/this/that/g'

在OSX上,类似的命令会是什么样子?


1
应该将其移至 apple.stackexchange.com,因为它既不够通用也不适用于所有开发人员。 - AlikElzin-kilaka
14个回答

302

OS X使用BSD和GNU工具的混合,所以最好始终检查文档(尽管我曾经发现less甚至不符合OS X manpage):

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

sed将在-i之后的参数作为备份的扩展名。提供一个空字符串(-i '')表示不备份。

以下命令应该可以实现:

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

-type f只是个好习惯;如果你给它一个目录,sed会抱怨。

-exec优先于xargs;你不需要担心-print0或其他任何事情。

结尾处的{} +表示find将所有结果追加为调用命令的一个实例的参数,而不是针对每个结果重新运行一次。(唯一的例外是当操作系统允许的命令行参数数量达到最大值时;在这种情况下,find将运行多个实例。)

如果出现像“无效的字节序列”之类的错误,可以在命令开头添加LC_ALL=C来强制使用标准语言环境,像这样:

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


3
在这个替换操作中,我的“this”包含一个正斜杠(localhost/site)--我正在替换.html文件中URL的部分......我该如何进行这样的替换。我尝试使用双引号,但失败了。 - Satchel
8
Sed 语法允许使用几乎 任何 字符来代替斜杠,例如你可以使用 % 符号:sed "s%localhost/site%blah/blah%"。另一种选择是使用反斜杠转义分隔符:sed "s/localhost\/site/blah\/blah/" - TaylanKammer
谢谢让我尝试。不过,我确实尝试使用{}来分隔斜杠,但仍然出现错误... - Satchel
21
还有人遇到“非法字节序列”错误吗?如果是这样,请尝试:LC_ALL=C find . -type f -name '*.txt' -exec sed -i '' s/this/that/ {} +,这对我有用。 - Caio Mar
14
这将仅替换文件中的一个匹配项,如果要替换多个,请使用 /g。例如:LC_ALL=C find . -type f -exec sed -i '' s/search/replace/g {} +。请注意,此命令会批量替换指定目录下所有类型为文件的文本内容中的搜索关键词。 - jamesjara
显示剩余17条评论

235

对于Mac电脑,更相似的方法是这样的:

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

21
我希望每次回来使用它时都能点赞,这样它现在就会轻松达到+15了。 - yurisich
1
由于某些原因,它对我不起作用。它什么也没做。我在文件夹form360内,并尝试将所有名称为easyform的字符串实例更改为form360,我正在运行以下命令:find . -name '*.php' -print0 | xargs -0 sed -i "" "s/easyform/form360/g" - Andres Ramos
2
对我来说,这应该是正确的答案。这是唯一一个对我有效的。 - nosequeldeebee
1
当文件名包含空格时,-print0 | xargs -0 在我的 Mac 上无法正常工作。 - acerphenix
1
sed:。:就地编辑仅适用于常规文件。 - codeslapper
点赞。真正解决了我的问题,关于重命名生产数据库转储文件 :) - Jukka Newkampton

26

作为替代方案,我在Mac OSX 10.7.5上使用了这个。

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

感谢原作者:Todd Cesere的回答


3
这个很好用!其他脚本在苹果电脑上某些情况下会多出一个换行符号!非常感谢! - Sebastien Filion
使用带有 * 的 grep 的缺点是速度会很慢。考虑使用 -I 来避免处理二进制文件,使用 --exclude-dir 或类似 {dir1,dir2} 的模式。 - zevarito
1
以下命令 $ grep -ilr 'fastapi' * | xargs -I@ sed -i '' 's/fastapi/flask/g' @ 无法正常工作,并输出以下内容: sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence - uberrebu
@uberrebu,它确实起作用了,但是您包含了一个参数来删除尾随换行符,而您的一个或多个目录包含二进制文件。幸运的是,sed失败了,而不是破坏文件。最好在具有某种版本控制方法(例如git)的文件夹中运行大规模破坏性命令,以防止不可逆转的损坏。 - Abandoned Cart

20

以上方法在OSX上都不适用。

请按照以下步骤操作:

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

1
如果SEARCH_FOR和REPLACE_WITH是路径,如何转义'/'? - rraallvv
使用不同的分隔符。如果您正在使用路径,则可以使用冒号或管道符号。例如,'s|SEARCH|REPLACE|g'。我们也可以使用大括号,如's{SEARCH}{REPLACE}'。 - dannysauer
1
有个问题,我在尝试在 Mac 上运行它,但似乎出现了错误?例如,我的路径被解释为一个文件?-bash: localhost/nohost: 没有那个文件或目录。 - Satchel
这个程序只会遍历一层文件夹,不会深入到子文件夹中。 - Sergey Romanov
有关此命令的更多信息,请阅读以下链接:https://lifehacker.com/5810026/quickly-find-and-replace-text-across-multiple-documents-via-the-command-line - kuzdu
grepfind的答案在Mac上都完美地运行了(而且效率更高)。 - Abandoned Cart

13

如果您正在使用zsh终端,您可以使用通配符魔法:

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


2
由于OSX现在默认具有ZSH终端,因此这应该是被接受的答案。 - Wladston Ferreira Filho
尝试了以上所有答案,但都没有起作用,除了这个,在Macbook Air M1 2021上。谢谢! - Allwyn Dsouza

8

一份可在Linux和Mac OS X上运行的版本(通过向sed添加-e开关):

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

我必须执行这个答案的导出操作,以及被接受的答案中的那一行(我不想生成备份文件)。 - Lance
2
为了解决“非法字节序列”错误,请在运行命令之前尝试设置LOCALE:export LC_CTYPE=C && export LANG=C - cjoy
1
如果你正在使用Git,请不要用'*'而不是'*.filetype'运行这个命令,否则所有未发布的工作都将消失。 - Sergey
1
sed 命令的 Mac 版本需要在 -i 后面加上 '',因此这个答案是不正确的。 - G Huxley

6

2021

对我有效的方法:

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

没有使用LC_ALL=C && LANG=C &&这部分,我就成功了。你为什么需要它呢? - Raphael Rafatpanah
否则,您可能会遇到ASCII到UTF转换的字节序列错误(可能取决于您使用的字符)。 - undefined

5

这是我在 Mac OS X 10.10.4 上可行的方案。

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

上述方法使用find命令会改变不包含搜索文本的文件(在文件末尾添加一个新行),这很啰嗦。

不起作用的 $ grep -e 'fastapi' -rl . | xargs sed -i '' 's/fastapi/flask/g',以下是输出结果:sed: RE error: illegal byte sequence - uberrebu

3
每当我输入这个命令时,我总是会搞砸或忘记一个标志。我在github上创建了一个基于TaylanUB答案的Gist,它可以从当前目录进行全局查找和替换。这是特定于Mac OSX的。

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

很好,因为现在我只需要打开终端,然后复制以下内容:

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

有时会出现一些奇怪的字节序列错误,所以这里是完整的代码:

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

1
我使用了这种格式,但是...我发现我不得不运行三次或更多次才能使它实际更改每个实例,这让我感到非常奇怪。 运行一次会更改每个文件中的某些内容,但不是全部。 运行完全相同的字符串两到四次将捕获所有实例。
find . -type f -name '*.txt' -exec sed -i '' s/thistext/newtext/ {} +

2
你需要多次运行此命令,因为你的sed正则表达式需要在末尾加上g,否则它只会替换一行中第一个出现的thistext。所以你的正则表达式应该是s/thistext/newtext/g - Les Nightingill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接