sed 就地标志,适用于 Mac (BSD) 和 Linux

Posted

技术标签:

【中文标题】sed 就地标志,适用于 Mac (BSD) 和 Linux【英文标题】:sed in-place flag that works both on Mac (BSD) and Linux 【发布时间】:2011-08-07 08:16:11 【问题描述】:

是否有在 Linux 和 Mac 上都可以使用的在没有备份的情况下调用 sed todo 就地编辑?虽然 OS X 附带的 BSD sed 似乎需要 sed -i '' …,但 GNU sed Linux 发行版通常将引号解释为空输入文件名(而不是备份扩展名),并且需要 sed -i …

是否有任何命令行语法适用于这两种风格,所以我可以在两个系统上使用相同的脚本?

【问题讨论】:

perl 不是一个选项吗? 必须您使用 sed 吗? 也许安装 GNU 版本并使用它! topbug.net/blog/2013/04/14/…?我会先试试。话又说回来,那也太糟糕了。 @dimadima:对于其他浏览这个问题的人来说可能会很有趣,他们的个人脚本在他们的 OS X 机器上崩溃了。不过,在我的情况下,我需要它来构建开源项目的构建系统,告诉您的用户首先安装 GNU sed 会破坏本练习的最初目的(以“随处可用”的方式修补一些文件) . @klickverbot 是的,有道理。我首先将评论添加为答案,然后将其删除,意识到这不是您问题的答案:)。 交叉引用:How to achieve portability with sed -i (in-place editing)? 在 Unix 和 Linux Stack Exchange 上。 【参考方案1】:

这适用于 GNU sed,但不适用于 OS X:

sed -i -e 's/foo/bar/' target.file
sed -i'' -e 's/foo/bar/' target.file

这适用于 OS X,但不适用于 GNU sed:

sed -i '' -e 's/foo/bar/' target.file

在 OS X 上你

无法使用sed -i -e,因为备份文件的扩展名将设置为-e 不能使用sed -i'' -e,原因相同——它需要在-i''之间有一个空格。

【讨论】:

@AlexanderMills 告诉 sed 下一个参数是命令 - 也可以在这里跳过。 请注意-i-i'' 在shell 解析时是相同的; sed 不能在这两个第一次调用中表现不同,因为它得到完全相同的参数。【参考方案2】:

回答:没有。

最初接受的答案实际上并没有按照要求执行(如 cmets 中所述)。 (我在寻找 file-e 在我的目录中“随机”出现的原因时找到了这个答案。)

显然没有办法让sed -i 在 MacOS 和 Linuces 上始终如一地工作。

我的建议是,不要使用sed(具有复杂的故障模式)就地更新,而是生成新文件并在之后重命名它们。换句话说:避免-i

【讨论】:

【参考方案3】:

Steve Powell's answer 非常正确,查阅 MAN 页面以了解 OSX 和 Linux (Ubuntu 12.04) 上的 sed 强调了两个操作系统之间“就地”使用 sed 的不兼容性。

仅供参考,使用 Linux 版本的 sed,-i 和任何引号(表示空文件扩展名)之间不应有空格,因此

sed Linux Man Page

#Linux
sed -i"" 

sed OSX Man page

#OSX (notice the space after the '-i' argument)
sed -i "" 

我通过在 bash 'if' 中使用别名命令和 'uname' 的操作系统名称输出在脚本中解决了这个问题。在解释引号时,尝试将依赖于操作系统的命令字符串存储在变量中会被击中并错过。为了扩展/使用脚本中定义的别名,必须使用“shopt -s expand_aliases”。 shopt的使用情况处理here。

【讨论】:

【参考方案4】:

没有办法让它工作。

一种方法是使用临时文件,例如:

TMP_FILE=`mktemp /tmp/config.XXXXXXXXXX`
sed -e "s/abc/def/" some/file > $TMP_FILE
mv $TMP_FILE some/file

这两个都适用

【讨论】:

是否有原因 SOME_FILE="$(sed -s "s/abc/def/" some/file)";回声“$SOME_FILE”> 一些/文件;不会起作用 看起来你会受到 bash 变量的最大大小的限制,不是吗?不确定它是否适用于 GBs 文件。 如果您正在创建一个临时文件,为什么不按照@kine 的建议提供一个扩展名来创建一个备份文件(然后您可以将其删除)?【参考方案5】:

你可以用海绵。 Sponge 是一个旧的 unix 程序,可以在 moreutils 包中找到(在 ubuntu 和可能的 debian 中,以及在 mac 中的 homebrew 中)。

它会缓冲来自管道的所有内容,等到管道关闭(可能意味着输入文件已经关闭)然后覆盖:

来自man page:

概要

sed '...' 文件 | grep '...' |海绵文件

【讨论】:

不错的方法 - 不幸的是,在最初的情况下并没有真正的帮助,因为使用 sed 的原因是在客户端机器上使用的跨平台帮助脚本中有一些东西可以使用,在那里你可以' t 依赖于除了正在安装的系统工具之外的任何东西。不过,可能对其他人有帮助。【参考方案6】:

如果您真的只想以“简单”的方式使用sed -i,以下在 GNU 和 BSD/Mac 上都可以使用sed

sed -i.bak 's/foo/bar/' filename

注意缺少空格和点。

证明:

# GNU sed
% sed --version | head -1
GNU sed version 4.2.1
% echo 'foo' > file
% sed -i.bak 's/foo/bar/' ./file
% ls
file  file.bak
% cat ./file
bar

# BSD sed
% sed --version 2>&1 | head -1
sed: illegal option -- -
% echo 'foo' > file
% sed -i.bak 's/foo/bar/' ./file
% ls
file  file.bak
% cat ./file
bar

显然你可以删除 .bak 文件。

【讨论】:

这是要走的路。添加一些字符,它是可移植的。删除备份文件很简单(调用sed时已经有了文件名) 这在 mac 上有效,但在 ubuntu 16.04 上它将原始文件覆盖为 0 字节并创建 .bak 文件也为 0 字节? 值得注意的是,在 macOS Sierra(可能更早的版本,我手边没有机器)上,sed 在使用 -i 调用时不接受位置参数 command — 它 必须提供另一个标志-e。因此,命令变为:sed -i.bak -e 's/foo/bar/' filename 这会创建备份,而 OP 特别要求就地编辑。 所以完整的解决方案是:sed -i.bak 's/foo/bar/' file && rm file.bak【参考方案7】:

正如Noufal Ibrahim 所问,为什么不能使用 Perl?任何 Mac 都会有 Perl,并且很少有 Linux 或 BSD 发行版在基本系统中不包含某些版本的 Perl。 BusyBox 可能实际上缺少 Perl 的唯一环境之一(它的工作方式类似于 GNU/Linux for -i,除了不能指定备份扩展)。

正如ismail 建议的那样,

因为 perl 随处可用,所以我只做 perl -pi -e s,foo,bar,g target.file

在处理 GNU/Linux 和 BSD/Mac 之间的 sed -i 的根本不兼容问题时,这似乎是比脚本、别名或其他解决方法更好的解决方案。

【讨论】:

我喜欢这个解决方案。 perl 自 2009 年 5 月 1 日起成为 Linux 标准基础规范的一部分。refspecs.linuxfoundation.org/LSB_4.0.0/LSB-Languages/… 这很容易成为便携性的最佳解决方案【参考方案8】:

在 OSX 上,我总是通过 Homebrew 安装 GNU sed 版本,以避免脚本出现问题,因为大多数脚本都是为 GNU sed 版本编写的。

brew install gnu-sed --with-default-names

那么你的 BSD sed 将被 GNU sed 取代。

或者,您可以不使用默认名称进行安装,但是:

安装gnu-sed后按照说明更改PATH 请检查您的脚本以在 gsedsed 之间进行选择,具体取决于您的系统

【讨论】:

--with-default-names 是removed from homebrew-core,更多信息请参见this 答案。现在安装gnu-sed 时,安装说明指定您需要将gnubin 添加到您的PATHPATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH" 自制程序包的页面 -- formulae.brew.sh/formula/coreutils -- 如果您希望它们在同名 Mac 工具之前的路径中,建议将其添加到您的 bashrcPATH="$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH"【参考方案9】:

以下内容适用于 Linux 和 OS X:

sed -i' ' <expr> <file>

例如对于包含 aaabbaaba 的文件 f

sed -i' ' 's/b/c/g' f

在 Linux 和 Mac 上产生 aaaccaaca。注意有一个带引号的字符串包含一个空格-i 和字符串之间没有空格。单引号或双引号都可以。

在 Linux 上,我在 Ubuntu 14.04.4 下使用 bash 版本 4.3.11,在 OS X 10.11.4 El Capitan (Darwin 15.4.0) 下使用 Mac 版本 3.2.57。

【讨论】:

哇,我很高兴我没有听上面的每个人说这是不可能的,并继续阅读!这真的很有效。谢谢! 在 Mac OS 上对我不起作用...创建了文件名末尾附加空格的新文件 在 Mac 上也不适用于我 (/usr/bin/sed) - 我现在有一个额外的备份文件,文件名后面附加了一个空格:-( 尝试从单引号中去掉空格,它对我有用。【参考方案10】:

-i 选项不是POSIX Sed 的一部分。一种更便携的方法是 在 Ex 模式下使用 Vim:

ex -sc '%s/alfa/bravo/|x' file

    %选择所有行

    s替换

    x保存并关闭

【讨论】:

这是正确答案。 POSIX 的一个关键特性是可移植性。【参考方案11】:

我遇到了这个问题。唯一快速的解决方案是将mac中的sed替换为gnu版本:

brew install gnu-sed

【讨论】:

这复制了一个现有的答案,其中包含 2015 年的更多细节。【参考方案12】:

如果您需要在bash 脚本中就地执行sed,并且您不希望就地生成 .bkp 文件,并且您有办法检测操作系统(例如,使用ostype.sh), -- 那么下面的bash shell 内置eval 应该可以工作:

OSTYPE="$(bash ostype.sh)"

cat > myfile.txt <<"EOF"
1111
2222
EOF

if [ "$OSTYPE" == "osx" ]; then
  ISED='-i ""'
else # $OSTYPE == linux64
  ISED='-i""'
fi

eval sed $ISED 's/2222/bbbb/g' myfile.txt
ls 
# GNU and OSX: still only myfile.txt there

cat myfile.txt
# GNU and OSX: both print:
# 1111
# bbbb

# NOTE: 
# if you just use `sed $ISED 's/2222/bbbb/g' myfile.txt` without `eval`,
# then you will get a backup file with quotations in the file name, 
# - that is, `myfile.txt""`

【讨论】:

【参考方案13】:

这是另一个在 Linux 和 macOS 上工作的版本,无需使用 eval,也无需删除备份文件。它使用 Bash 数组来存储 sed 参数,这比使用 eval 更干净:

# Default case for Linux sed, just use "-i"
sedi=(-i)
case "$(uname)" in
  # For macOS, use two parameters
  Darwin*) sedi=(-i "")
esac

# Expand the parameters in the actual call to "sed"
sed "$sedi[@]" -e 's/foo/bar/' target.file

这不会创建备份文件,也不会创建带有附加引号的文件。

【讨论】:

这是正确答案。我会稍微简化一下,但这个想法非常有效sedi=(-i) &amp;&amp; [ "$(uname)" == "Darwin" ] &amp;&amp; sedi=(-i '')sed "$sedi[@]" -e 's/foo/bar/' target.file 好东西!不过,为了便于阅读,我更喜欢较长的版本。 这是我兼容不同系统的最佳方式。【参考方案14】:

适用于 GNU 系统和 OSX 的可移植脚本:

if [[ $(uname) == "Darwin" ]]; then
    SP=" " # Needed for portability with sed
fi

sed -i$SP'' -e "s/foo/bar/g" -e "s/ping/pong/g" foobar.txt

【讨论】:

没有理由在"Darwin" 周围加上引号——它只能解析为自身。而如果不是因为 [[ ]] 的特殊语义,您需要在 $(uname) 周围加上引号,以确保所有可能的返回值的正确行为。

以上是关于sed 就地标志,适用于 Mac (BSD) 和 Linux的主要内容,如果未能解决你的问题,请参考以下文章

带有 -i 选项的 sed 命令(就地编辑)在 Ubuntu 上运行良好,但在 Mac 上运行良好 [重复]

AIX 上的 Sed 无法识别 -i 标志

Mac 上简单使用 Sed

sed 自动就地替换吗?

作为sed替代方案的Perl就地替换

使用 sed 在 bash 脚本中进行就地重命名