如何在 macOS 上使用 egrep 和 sed 进行搜索和替换?

How to search and replace with egrep and sed on macOS?

我想匹配文件中的模式并替换它。

This command 与 egrep、xargs 和 sed 一起工作:

egrep -lRZ "hello" . | xargs -0 -l sed -i -e 's/hello/world/g'

问题:在MacOS上不工作,因为MacOS的xargs不支持参数-l。

xargs: illegal option -- l
usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]
             [-L number] [-n number [-x]] [-P maxprocs] [-s size]
             [utility [argument ...]]

这在 MacOS 上如何解决?

GNU (Linux) 与 bsd (macOS) 实用程序之间实际上存在三个不兼容问题 运行。

  • 您收到的错误消息是 bsd 的 xargs 不接受 -l 选项。但是 -l 等同于 -L 除了 -L 需要 一个参数指定每次调用命令传递的最大行数,而 -l 如果未指定,则默认为 1。因此,您可以将 -l 替换为 -L1-L is GNU 和 bsd 版本的 xargs 以相同的方式理解,因此使用它可以在 Linux 和 macOS 之间移植。

    但在这种特殊情况下,还有另一个更简单的选择:sed 完全能够每次调用操作多个文件,因此没有理由将其限制为每次调用一个。这甚至会稍微快一些,因为它不必花费太多时间来启动新进程。所以请关闭 -l

  • egrep 的 GNU 和 bsd 版本(以及 grep 系列中的其他版本)都采用选项 -Z,但他们用它来表示完全不同的东西。使用 GNU,egrep -Z 在每个文件名后打印零字节(ASCII NUL 字符)(匹配 xargs -0 所期望的)。但是对于 bsd,egrep -Z 等同于 zgrep -- 它将其输入文件视为 zip 存档,并在搜索其内容之前展开它们。

    幸运的是,两个版本都理解 --null 调用零字节定界符,因此您可以在两个平台上方便地使用它。

  • GNU 和 bsd 版本都将 -i<suffix> 理解为“就地编辑,但制作备份副本,并使用指定的文件名后缀备份原始文件”。对于它们两者,如果后缀为零长度,则不会保留备份。不幸的是,您指定零长度后缀的方式是不同的,并且(据我所知)不可调和地不兼容。具体来说,GNU 要求后缀直接附加到 -i(例如 -i.bkp),因此只需指定 -i 本身就足以指定 in-place-without-backup 模式。但是 bsd 允许将后缀作为单独的参数传递(例如 -i .bkp),因此如果您只指定 -i 本身,它将使用下一个参数作为后缀(例如 sed -i -e 's/hello/world/g' 将使用“-e”作为后缀)。要指定就地无备份模式,您需要在 -i 后跟一个明确的空参数(例如 sed -i '' -e 's/hello/world/g')。但是,如果您使用 GNU 的 sed 执行此操作,它将尝试将空参数作为其脚本执行,这将失败。

综上所述,这是您的命令的 macOS 版本:

egrep -lR --null "hello" . | xargs -0 sed -i '' -e 's/hello/world/g'

...这将 几乎 在 Linux 上工作——唯一的区别是您需要删除 [=21] 的 '' 参数=].如果你想要在 Linux 和 macOS 之间完全可移植的东西,你需要指定一个备份后缀(并将其直接附加到 -i 选项,如 -i.bkp)。

最好避免递归搜索文件的 grep 选项 - 它们只会弄乱您的 grep args 并使您的脚本不可移植。已经有一个非常好的工具可以用来找到 名字很明显的文件。

您是否只是想将所有文件中的 hello 替换为 world?如果是这样,那只是

find . -type f |
while IFS= read -r file; do
    sed 's/hello/world/g' "$file" > "tmp$$" &&
    mv "tmp$$" "$file"
done

除非您的文件名包含换行符,否则这将适用于任何 UNIX 机器上的任何 shell。如果您不想更改不包含 hello 的文件的时间戳等,一种方法是:

find . -type f -exec grep -q 'hello' {} \; -print |
while IFS= read -r file; do
    sed 's/hello/world/g' "$file" > "tmp$$" &&
    mv "tmp$$" "$file"
done