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

时间:2019-02-02 21:00:23

标签: bash macos sed grep xargs

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

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?

2 个答案:

答案 0 :(得分:2)

在GNU(Linux)实用工具和bsd(macOS)实用工具之间实际上会遇到三种不兼容性。

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

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

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

    幸运的是,两个版本都了解zgrep来调用零字节定界符,因此您可以在两个平台上都可移植地使用它。

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

所有这些,这是命令的macOS版本:

sed -i '' -e 's/hello/world/g'

...几乎可以在Linux上工作的

...唯一的区别是您需要删除sed的{​​{1}}参数。如果要在Linux和macOS之间完全移植,则需要指定备份后缀(并将其直接附加到egrep -lR --null "hello" . | xargs -0 sed -i '' -e 's/hello/world/g' 选项,如''所示)。

答案 1 :(得分:2)

最好避免使用grep递归搜索文件的选项-它们只会使grep arg杂乱无章,并使脚本不可移植。已经有一个非常好的工具,可以用来查找名称非常明显的文件。

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

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

除非您的文件名包含换行符,否则它将在任何UNIX盒的任何外壳程序中都有效。如果您不想更改不包含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