macos 用于就地编辑的 sed -i 命令,用于 GNU sed 和 BSD/OSX

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2320564/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 06:47:11  来源:igfitidea点击:

sed -i command for in-place editing to work with both GNU sed and BSD/OSX

macossedgnuinplace-editing

提问by Chris Tonkinson

I've got a makefile (developed for gmakeon Linux) that I'm attempting to port to MacOS, but it seems like seddoesn't want to cooperate. What I do is use GCCto autogenerate dependency files, and then tweak them a bit using sed. The relevant portion of the makefile:

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

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

While this runs with no trouble under GNU/Linux, I get errors like the following when attempting to build on MacOS:

虽然这在 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'

It would seem like sedis chopping off a character, but I can't see the solution.

看起来像是sed砍掉了一个角色,但我看不到解决方案。

回答by martin clayton

OS X sedhandles the -iargument differently to the Linuxversion.

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

You can generate a command that might "work" for both by adding -ein this way:

您可以通过-e以这种方式添加来生成一个可能对两者都“有效”的命令:

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

OS X sed -iinterprets the next thing after the -ias a file extension for a backup copyof the in-place edit. (The Linux version only does this if there is no space between the -iand the extension.) Obviously a side affect of using this is that you will get a backup file with -eas an extension, which you may not want.Please refer to other answers to this question for more details, and cleaner approaches that can be used instead.

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

The behaviour you see is because OS X sedconsumes the s|||as the extension (!) then interprets the nextargument as a command - in this case it begins with t, which sedrecognizes as a branch-to-label command expecting the target label as an argument - hence the error you see.

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

If you create a file testyou can reproduce the error:

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

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

回答by Urkle

Actually, doing

其实,做

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

doesn't do what you expect in MacOS either. Instead it creates backup files with -eextension.

也不会在 MacOS 中执行您期望的操作。相反,它会创建带有-e扩展名的备份文件。

The proper command for MacOS is

MacOS 的正确命令是

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

On Linux, remove the space between -iand ""(see related answer)

在Linux上,除去之间的空间-i""(见相关答案

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

回答by arctelix

The currently accepted answer is flawed in two very important ways.

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

  1. With BSD sed (the OSX version), the -eoption is interpreted as a file extension and therefore creates a backup file with a -eextension.

  2. Testing for the darwin kernel as suggested is not a reliable approach to a cross platform solution since GNU or BSD sed could be present on any number of systems.

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

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

A much more reliable test would be to simply test for the --versionoption which is only found in the GNU version of sed.

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

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

Once the correct version of sed is determined, we can then execute the command in its proper syntax.

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

GNU sed syntax for -i option:

-i 选项的 GNU sed 语法:

sed -i -- "$@"

BSD sed syntax for -i option:

-i 选项的 BSD sed 语法:

sed -i "" "$@"

Finally put it all together in a cross platform function to execute an in place edit sed commend:

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

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

Example usage:

用法示例:

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

This solution has been tested on OSX, Ubuntu, Freebsd, Cygwin, CentOS, Red Hat Enterprise, & Msys.

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

回答by Anthony Sottile

This isn't quite an answer to the question, but one can get linux-equivalent behavior through

这不是对问题的完全答案,但是可以通过以下方式获得与 linux 等效的行为

brew install gnu-sed

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

(previously there was a --with-default-namesoption to brew install gnu-sedbut that has recently been removed)

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

回答by mklement0

martin clayton's helpful answerprovides a good explanation of the problem[1], but a solution that - as he states - has a potentially unwanted side effect.

马丁克莱顿的有用答案很好地解释了问题[1],但解决方案 - 正如他所说 - 具有潜在的不需要的副作用。

Here are side-effect-free solutions:

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

Caveat: Solving the -isyntax problem alone, as below, may not be enough, because there are many other differences between GNU sedand BSD/macOS sed(for a comprehensive discussion, see this answerof mine).

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



Workaround with -i: Create a backup file temporarily, then clean it up:

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

With a non-emptysuffix (backup-file filename extension) option-argument (a value that is not the empty string), you canuse -iin a way that works with both BSD/macOS sedand GNU sed, by directly appending the suffix to the -ioption.

随着非空的后缀(备份文件扩展名)选项参数(这是一个值不是空字符串),你可以使用-i的方式,既BSD / MacOS的作品sed和GNU sed,通过后缀直接附加到-i选项

This can be utilized to create a backup file temporarilythat you can clean up right away:

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

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

Obviously, if you do want to keep the backup, simply omit the && rm file.bakpart.

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



Workaround that is POSIX-compliant, using a temporary file and mv:

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

If only a singlefile is to be edited in-place, the -ioption can be bypassedto avoid the incompatibility.

如果只对单个文件进行就地编辑,-i可以绕过选项以避免不兼容。

If you restrict your sedscript and other options to POSIX-compliant features, the following is a fully portablesolution (note that -iis notPOSIX-compliant).

如果你限制你的sed脚本和其他选项,以符合POSIX标准的功能,下面是一个完全便携式的解决方案(注意,-i符合POSIX标准)。

sed 's/foo/bar' file > /tmp/file.$$ && mv /tmp/file.$$ file
  • This command simply writes the modifications to a temporary file and, if the sedcommand succeeds (&&), replaces the original file with the temporary one.

    • If you do want to keep the original file as a backup, add another mvcommand that renames the original first.
  • Caveat: Fundamentally, this is what -idoes too, except that it tries to preserve permissions and extended attributes (macOS) of the original file; however, if the original file is a symlink, both this solution and -iwill replace the symlink with a regular file.
    See the bottom half of this answerof mine for details on how -iworks.

  • 此命令只是将修改写入临时文件,如果sed命令成功 ( &&),则将原始文件替换为临时文件。

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



[1] For a more in-depth explanation, see this answerof mine.

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

回答by thecarpy

I came across this issue as well and thought of the following solution:

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

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

Works for me ;-), YMMV

对我有用 ;-), YMMV

I work in a multi-OS team where ppl build on Windows, Linux and OS X. Some OS X users complained because they got another error - they had the GNU port of sed installed so I had to specify the full path.

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

回答by niieani

I've corrected the solution posted by @thecarpy:

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

Here's a proper cross-platform solution for sed -i:

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

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

  LC_ALL=C sed "${sedi[@]}" "$@"
}