在Mac (BSD)和Linux上都可以工作的sed in-place标志

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

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

105097 次浏览

这适用于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''之间有一个空格。

< >强答: < / >强。

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

显然没有办法让sed -i在MacOS和linux上一致地工作。

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

史蒂夫·鲍威尔的回答是非常正确的,在OSX和Linux (Ubuntu 12.04)上咨询sed的MAN页面突出了跨两个操作系统的“就地”sed使用中的不兼容性。

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

sed Linux手册

#Linux
sed -i""

而且

sed OSX Man page

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

我在一个脚本中通过使用别名命令和bash 'if'中的'uname'的OS-name输出来解决这个问题。在解释引号时,试图将依赖于操作系统的命令字符串存储在变量中是错误的。为了扩展/使用脚本中定义的别名,必须使用'shop -s expand_aliases'。shop的用法被处理为在这里

没有办法让它正常工作。

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

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

这对两者都有效

你可以用海绵。海绵是一个老的unix程序,在moreutils包中找到(在ubuntu和debian中都有,在mac的homebrew中也有)。

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

手册页:

剧情简介

sed的……' file | grep '…|海绵文件

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

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文件。

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

正如伊斯梅尔所建议的,

由于perl在任何地方都可用,所以我只执行perl -pi -e s,foo,bar,g target.file

在几乎任何情况下,这似乎都比脚本、别名或其他变通方法更好地解决了sed -i在GNU/Linux和BSD/Mac之间的根本不兼容问题。

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

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

然后你的BSD sed将被GNU sed取代。

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

  • 在安装gnu-sed后,按照指示更改你的PATH
  • 检查你的脚本在gsedsed之间选择取决于你的系统

以下是我在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。

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

ex -sc '%s/alfa/bravo/|x' file
  1. %选择所有行

  2. s取代

  3. x保存并关闭

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

brew install gnu-sed

如果你需要在bash脚本中执行sed in-place,并且你不希望in-place产生.bkp文件,并且你有一种方法来检测操作系统(例如,使用ostype.sh),那么下面的使用bash shell内置eval的hack应该有效:

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""`

下面是另一个在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

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

适用于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

问题是sed是一个编辑器,因此就地编辑是一个非posix扩展,每个人实现它的方式可能不同。这意味着对于就地编辑,你应该使用ed来获得最佳的可移植性。如。

ed -s foobar.txt <<<$',s/foo/bar/g\nw'

也可参见https://wiki.bash-hackers.org/howto/edit-ed