我需要我的sed -i命令进行就地编辑,以使用GNU sed和BSD / OSX sed

我有一个makefile(为Linux上的gmake开发),我试图移植到OSX,但它似乎像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下运行时没有问题,但是在尝试在OSX上构build时遇到以下错误:

 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正在砍掉一个angular色,但我看不到解决scheme。

OS X sed以不同于Linux版本的方式处理-i参数。

您可以通过以这种方式添加-e来生成一个可能“工作”的命令:

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

OS X sed -i-i后面的内容解释为就地编辑的备份副本的文件扩展名。 (Linux版本只有在-i和扩展名之间没有空格的情况下才-i )。 显然,使用它的一个副作用是你将得到一个备份文件,其中-e作为扩展名,你可能不需要。 请参阅此问题的其他答案以获取更多详细信息,以及可以使用的更简洁的方法。

您看到的行为是因为OS X sed使用s

作为扩展名(!),然后将下一个参数解释为一个命令 – 在这种情况下,它以t开头, sed目标标签作为参数来识别分支到标签的命令 – 因此会出现错误。

如果你创build一个文件test你可以重现错误:

 $ sed -i 's|x|y|' test sed: 1: "test": undefined label 'est' 

真的..做

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

在OS X中没有达到您所期望的效果。相反,它会创build带“-e”扩展名的备份文件。

适合的操作系统是

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

目前接受的答案有两个非常重要的缺陷。

  1. 使用BSD sed(OSX版本)时, -e选项被解释为文件扩展名,因此创build带-e扩展名的备份文件。

  2. 按照build议testing达尔文内核对于跨平台解决scheme来说不是可靠的方法,因为GNU或BSD sed可能出现在任何数量的系统上。

一个更可靠的testing将是简单地testing--version选项,它只能在GNU版本的sed中find。

 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' 

此解决scheme已在OSX,Ubuntu,Freebsd,Cygwin,CentOS,Red Hat Enterprise和Msys上进行testing。

这不是问题的答案,但可以通过brew install gnu-sed --with-default-names获得与linux相当的行为

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

 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

我在一个多操作系统团队中工作,在Windows,Linux和OS X上构build这个团队。一些OS X用户抱怨,因为他们有另一个错误 – 他们安装了sed的GNU端口,所以我必须指定完整的path。

马丁·克莱顿的有用答案为这个问题提供了一个很好的解释[1] ,但是正如他所说的,一个解决scheme有一个潜在的不必要的副作用。

这里是无副作用的解决scheme

警告 :单独解决-i语法问题可能还不够,因为GNU sed和BSD / macOS sed之间还有很多其他的区别(综合讨论见我的这个答案 )。


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

使用非空后缀(备份文件文件扩展名)选项参数(值不是空string ),您可以使用-i与BSD / macOS sed和GNU sed ,方法是直接附加-i选项的后缀

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

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

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


符合POSIX的解决方法是使用临时文件和mv

如果只有一个文件要在原地进行编辑, 则可以绕过 -i选项以避免不兼容。

如果您将sed脚本和其他选项限制为符合POSIX标准的function ,以下是完全便携式解决scheme(请注意, -i 符合POSIX标准 )。

 sed 's/foo/bar' file > /tmp/file.$$ && mv /tmp/file.$$ file 
  • 这个命令只是把修改写到一个临时文件中,如果sed命令成功( && ),就用临时文件replace原来的文件。

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


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

我已更正@thecarpy发布的解决scheme:

以下是适合sed -i跨平台解决scheme:

 sedi() { case $(uname) in Darwin*) sedi=('-i' '') ;; *) sedi='-i' ;; esac LC_ALL=C sed "${sedi[@]}" "$@" }