我需要我的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
目前接受的答案有两个非常重要的缺陷。
-
使用BSD sed(OSX版本)时,
-e
选项被解释为文件扩展名,因此创build带-e
扩展名的备份文件。 -
按照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[@]}" "$@" }