我有一个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时会出现如下错误:
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正在砍掉一个角色,但我看不到解决方案.
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 X sed -i
解释了-i
作为就地编辑的备份副本的文件扩展名之后的下一件事.(只有-i
在扩展名和扩展名之间没有空格时,Linux版本才会执行此操作.)显然,使用此版本的一个副作用是,您将获得一个备份文件-e
作为扩展名,您可能不需要.有关更多详细信息,请参阅此问题的其他答案,以及可以使用的更清晰的方法.
您所看到的行为是因为OS X sed
消耗s|||
作为扩展名,然后解释(!),接下来的参数作为一个命令-在这种情况下,它开头t
,其sed
识别为一个分支到标签的命令预期目标标签作为参数-因此你看到的错误.
如果您创建文件test
,则可以重现错误:
$ sed -i 's|x|y|' test
sed: 1: "test": undefined label 'est'
实际上......做
sed -i -e "s/blah/blah/" files
不会在OS X中执行您所期望的...而是创建具有"-e"扩展名的备份文件.
适合OS的是
sed -i "" -e "s/blah/blah/" files
目前接受的答案有两个非常重要的缺陷.
使用BSD sed(OSX版本),该-e
选项被解释为文件扩展名,因此创建具有扩展名的备份文件-e
.
所建议的对darwin内核的测试不是跨平台解决方案的可靠方法,因为GNU或BSD sed可能存在于任意数量的系统上.
一个更可靠的测试是简单地测试--version
只在sed的GNU版本中找到的选项.
sed --version >/dev/null 2>&1
一旦确定了正确版本的sed,我们就可以用正确的语法执行命令.
-i选项的GNU sed语法:
sed -i -- "$@"
BSD sed -i选项的语法:
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上进行了测试.
这不是问题的答案,但可以通过以下方式获得与Linux等效的行为
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
但最近已被删除)
我也遇到了这个问题并想到了以下解决方案:
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端口,所以我必须指定完整路径.
马丁克莱顿的有用答案提供了一个很好的解释问题[1],但一个解决方案 - 正如他所说 - 具有潜在的不良副作用.
这是无副作用的解决方案:
警告:-i
单独解决语法问题,如下所示,可能还不够,因为GNU sed
和BSD/macOS 之间还有许多其他差异sed
(有关综合讨论,请参阅我的这个答案).
-i
:临时创建备份文件,然后进行清理:使用非空后缀(备份文件文件扩展名)option-argument(不是空字符串的值),您可以使用-i
BSD/macOS sed
和GNU的方式sed
,直接将后缀附加到-i
选项.
这可用于暂时创建备份文件,您可以立即清理:
sed -i.bak 's/foo/bar/' file && rm file.bak
显然,如果你想保留备份,只需省略该&& rm file.bak
部分即可.
mv
:如果只需要就地编辑单个文件,则-i
可以绕过该选项以避免不兼容.
如果将sed
脚本和其他选项限制为符合POSIX的功能,则以下是完全可移植的解决方案(请注意,-i
它不符合POSIX标准).
sed 's/foo/bar' file > /tmp/file.$$ && mv /tmp/file.$$ file
此命令只是将修改写入临时文件,如果sed
命令成功(&&
),则将原始文件替换为临时文件.
如果您确实希望将原始文件保留为备份,请添加另一个mv
首先重命名原始文件的命令.
警告:从根本上说-i
,除了它试图保留原始文件的权限和扩展属性(macOS)之外,这也是如此.但是,如果原始文件是符号链接,则此解决方案-i
都将使用常规文件替换符号链接.
有关工作原理的详细信息,请参阅本答案的下半部分-i
.
[1]有关更深入的解释,请参阅我的这个答案.