69

我有一个gmake试图移植到 MacOS 的 makefile(为 Linux 开发),但似乎sed不想合作。我所做的是使用GCC自动生成依赖文件,然后使用sed. 的相关部分makefile

$(OBJ_DIR)/%.d: $(SRC_DIR)/%.cpp
  $(CPPC) -MM -MD $< -o $@
  sed -i 's|\(.*\)\.o:|$(OBJ_DIR)/\1.o $(OBJ_DIR)/\1.d $(TEST_OBJ_DIR)/\1_utest.o:|' $@

虽然这在 GNU/Linux 下运行没有问题,但在尝试在 MacOS 上构建时出现以下错误:

sed: 1: "test/obj/equipmentConta ...": undefined label 'est/obj/equipmentContainer_utest.d'
sed: 1: "test/obj/dice_utest.d": undefined label 'est/obj/dice_utest.d'
sed: 1: "test/obj/color-string_u ...": undefined label 'est/obj/color-string_utest.d'

似乎sed正在砍掉一个角色,但我看不到解决方案。

4

7 回答 7

67

OS Xsed处理-i参数的方式与Linux版本不同。

您可以通过-e以下方式添加一个可能对两者都“起作用”的命令:

#      vv
sed -i -e 's|\(.*\)\.o:|$(OBJ_DIR)/\1.o $(OBJ_DIR)/\1.d $(TEST_OBJ_DIR)/\1_utest.o:|' $@

OS Xsed -i将 之后的下一个内容解释为就地编辑的备份副本-i的文件扩展名。(Linux 版本只有在和 扩展名之间没有空格时才会这样做。)显然,使用它的副作用是您将获得一个带有扩展名的备份文件,这可能是您不想要的。请参阅此问题的其他答案以获取更多详细信息,以及可以使用的更清洁的方法。-i-e

您看到的行为是因为 OS Xseds|||用作扩展名(!)然后将下一个参数解释为命令 - 在这种情况下,它以 开头t,它sed识别为期望目标标签作为参数的分支到标签命令 -因此你看到的错误。

如果您创建一个文件test,您可以重现该错误:

$ sed -i 's|x|y|' test
sed: 1: "test": undefined label 'est'
于 2010-02-23T21:41:30.530 回答
59

其实,做

sed -i -e "s/blah/blah/" files

也不符合您在 MacOS 中的预期。-e相反,它会创建带有扩展名的备份文件。

MacOS 的正确命令是

sed -i "" -e "s/blah/blah/" files

在 Linux 上,删除 and 之间的空格-i""请参阅相关答案

sed -i"" -e "s/blah/blah/" files
于 2010-03-10T21:06:22.527 回答
45

当前接受的答案在两个非常重要的方面存在缺陷。

  1. 对于 BSD sed(OSX 版本),该-e选项被解释为文件扩展名,因此会创建一个带有-e 扩展名的备份文件。

  2. 按照建议对 darwin 内核进行测试并不是跨平台解决方案的可靠方法,因为 GNU 或 BSD sed 可能存在于任何数量的系统上。

更可靠的测试是简单地测试--version仅在 GNU 版本的 sed 中找到的选项。

sed --version >/dev/null 2>&1

一旦确定了 sed 的正确版本,我们就可以以正确的语法执行该命令。

-i 选项的 GNU sed 语法:

sed -i -- "$@"

-i 选项的 BSD sed 语法:

sed -i "" "$@"

最后将它们放在一个跨平台函数中以执行就地编辑 sed 命令:

sedi () {
    sed --version >/dev/null 2>&1 && sed -i -- "$@" || sed -i "" "$@"
}

示例用法:

sedi 's/old/new/g' 'some_file.txt'

该解决方案已在 OSX、Ubuntu、Freebsd、Cygwin、CentOS、Red Hat Enterprise 和 Msys 上进行了测试。

于 2016-07-26T16:22:27.483 回答
15

马丁克莱顿的有用回答很好地解释了这个问题[1],但正如他所说的那样,一个解决方案具有潜在的不良副作用。

以下是无副作用的解决方案

警告-i单独解决语法问题,如下所示,可能还不够,因为 GNUsed和 BSD/macOS之间还有许多其他差异sed(有关全面讨论,请参阅我的这个答案)。


解决方法:临时-i创建一个备份文件,然后清理它:

使用非空后缀(备份文件扩展名)选项参数(不是空字符串的值) ,您可以通过直接将后缀附加到-iBSD/macOSsed和 GNU来使用选项sed-i

这可以用来临时创建一个备份文件,您可以立即清理:

sed -i.bak 's/foo/bar/' file && rm file.bak

显然,如果您确实想保留备份,只需省略该&& rm file.bak部分即可。


使用临时文件和 POSIX 兼容的解决方法mv

如果仅要就地编辑单个文件,则可以绕过-i选项以避免不兼容。

如果您将sed脚本和其他选项限制为符合 POSIX 的功能,则以下是一个完全可移植的解决方案(请注意,-i符合 POSIX 标准)。

sed 's/foo/bar' file > /tmp/file.$$ && mv /tmp/file.$$ file
  • 此命令只是将修改写入临时文件,如果sed命令成功 ( &&),则将原始文件替换为临时文件。

    • 如果您确实想保留原始文件作为备份,请添加另一个mv命令首先重命名原始文件。
  • 警告:从根本上说,这也是这样-i做的,除了它试图保留原始文件的权限和扩展属性(macOS);但是,如果原始文件是符号链接,则此解决方案-i都将用常规文件替换符号链接。有关如何工作的详细信息,请参阅我的这个答案
    的下半部分。-i


[1] 有关更深入的解释,请参阅我的这个答案

于 2017-07-03T03:12:54.613 回答
14

这不是问题的完全答案,但可以通过

brew install gnu-sed

# Add to .bashrc / .zshrc
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"

(以前有一个--with-default-names选项,brew install gnu-sed但最近已被删除)

于 2014-12-22T02:11:38.093 回答
11

我也遇到了这个问题,并想到了以下解决方案:

darwin=false;
case "`uname`" in
  Darwin*) darwin=true ;;
esac

if $darwin; then
  sedi="/usr/bin/sed -i ''"
else
  sedi="sed -i"
fi

$sedi 's/foo/bar/' /home/foobar/bar

为我工作;-),YMMV

我在一个多操作系统团队工作,ppl 在 Windows、Linux 和 OS X 上构建。一些 OS X 用户抱怨,因为他们遇到了另一个错误——他们安装了 sed 的 GNU 端口,所以我必须指定完整路径。

于 2014-01-06T13:58:56.023 回答
2

我已经更正了@thecarpy 发布的解决方案:

这是一个适当的跨平台解决方案sed -i

sedi() {
  case $(uname) in
    Darwin*) sedi=('-i' '') ;;
    *) sedi='-i' ;;
  esac

  LC_ALL=C sed "${sedi[@]}" "$@"
}
于 2016-03-02T20:10:53.977 回答