sed就地标志,在Mac (BSD)和Linux上都有效



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

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

如果你真的想以简单的方式使用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文件。

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

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

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

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

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

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

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

按照ismail的建议,

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

,这似乎是一个更好的解决方案,在几乎任何情况下,比脚本,别名,或其他变通,以处理GNU/Linux和BSD/Mac之间的sed -i的根本不兼容性。

这是另一个版本,可以在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

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

答:

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

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

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

没有办法让它工作。

一种方法是使用临时文件,如:
TMP_FILE=`mktemp /tmp/config.XXXXXXXXXX`
sed -e "s/abc/def/" some/file > $TMP_FILE
mv $TMP_FILE some/file

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

ex -sc '%s/alfa/bravo/|x' file
  1. % select all lines

  2. s replace

  3. x保存并关闭

Steve Powell的回答是非常正确的,在OSX和Linux (Ubuntu 12.04)上查询sed的MAN页面强调了在两个操作系统上"就地"使用sed的不兼容性。

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

sed Linux Man Page

#Linux
sed -i"" 

sed OSX手册

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

我在脚本中通过使用别名命令和在bash 'if'中使用'uname'的OS-name输出来解决这个问题。在解释引号时,试图将操作系统相关的命令字符串存储在变量中会失败。为了扩展/使用脚本中定义的别名,必须使用'shopt -s expand_aliases'。这里讨论Shopt的用法。

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

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

brew install gnu-sed

如果您需要在bash脚本中执行sed就地执行,并且您不希望就地执行结果为.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""`

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

ed -s foobar.txt <<<$',s/foo/bar/gnw'

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

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

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

从手册页:

简介

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

以下方法适用于我在Linux和OS X上:

sed -i' ' <expr> <file>

。对于包含aaabbaaba的文件f

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

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

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

最新更新