使用带有-i选项的sed命令(原地编辑)在Ubuntu上运行良好,但在Mac上不起作用。

181

我对sed一无所知,但需要这个命令(在Ubuntu上正常工作)在Mac OSX上也能正常运行:

sed -i "/ $domain .*#drupalpro/d" /etc/hosts

我得到了:

sed: 1: "/etc/hosts": extra characters at the end of h command

@AnthonySottile:建议的重复问题确实密切相关,但与此问题不同的是,它明确要求解决方案适用于BSD/macOS和GNU实现的sed。 因此,对于只需要BSD/macOS解决方案的人来说,在这里可能会找到更专注的答案。 - mklement0
@mklement0 是的,问题略有不同,但两个问题的答案几乎相同 - 在我看来,没有必要有两个问题 耸肩 - anthony sottile
1
@AnthonySottile:这只是一个建议 - 无论哪种方式都可以 - 让我们让未来的关闭投票者决定。 - mklement0
也许,BashX 项目可以帮助您解决这类问题。 - Eduardo Cuomo
4个回答

237

Ubuntu预装GNU sed,其中-i选项的后缀是可选的。而OS X则预装BSD sed,其中该后缀是强制性的。请尝试使用sed -i ''


2
所以只需在sed -i后面加上单引号,sed -i '1i export PATH="$HOME/.composer/vendor/bin:$PATH"' $HOME/.bashrc 对我仍然不起作用。 - pal4life
21
@pal4life 的意思是,在执行命令之前需要一个单独的引用符号集,因此类似这样的东西 sed -i '' '1i export PATH="$HOME/.composer/vendor/bin:$PATH"' $HOME/.bashrc - microtherion
6
如果你需要在macOS(OS X,BSD)和Linux等平台上运行相同的脚本,那么你必须使用备份后缀(例如.bak),并将其附加到-i选项中—— sed -i.bak …。如果不这样做,则该脚本将无法在其中一个平台上正确操作。GNU sed 不喜欢 BSD sed 需要一个空参数才能原地编辑;你不能将空参数附加到 -i 上,因为它是空的,并且无法与 -i 区分。此外,由于设计上的矛盾,-i 选项也不会作为 -i 标准化为 POSIX;无法调和不可调和的冲突。 - Jonathan Leffler
3
如果你不需要备份,可以在运行sed命令后运行rm -f /etc/hosts.bak来删除备份。这比尝试确定安装的sed变体或其他建议中提到的小工具更简单。希望你能对文件进行版本控制(或备份),以便在sed脚本出现错误时进行恢复。当然,.bak文件为你提供了这种保护。 - Jonathan Leffler
也许,BashX 项目可以帮助您解决这类问题。 - Eduardo Cuomo

195
为了补充 microtherion 的简明有用的回答:
  • 使用便携式解决方案
  • 提供背景信息
简而言之: 这个命令的等效方式是 GNU sed(大多数 Linux 发行版上的标准):
sed -i    's/foo/bar/' file

这是一个BSD/macOS sed命令:
sed -i '' 's/foo/bar/' file  # Note the '' as a *separate argument*

使用BSD/macOS的sed,以下命令要么完全无法工作,要么不能按预期工作:
sed -i    's/foo/bar/' file  # Breaks; script is misinterpreted as backup-file suffix
sed -i''  's/foo/bar/' file  # Ditto
sed -i -e 's/foo/bar/' file  # -e is misinterpreted as backup-file suffix

关于 GNU sed 和 BSD/macOS sed 之间的所有差异的讨论,请参见我的 这个答案

可移植方法

注:这里的“可移植”是指该命令适用于所讨论的两种实现。它在 POSIX 意义上不是可移植的,因为 -i 选项不符合 POSIX 标准

# Works with both GNU and BSD/macOS Sed, due to a *non-empty* option-argument:
# Create a backup file *temporarily* and remove it on success.
sed -i.bak 's/foo/bar/' file && rm file.bak

解释请见下文;若需其他解决方案,包括符合 POSIX 标准的方案,请参阅我提供的相关答案

背景信息

在大多数Linux发行版中,GNUsedBSD/macOSsed都是标准的。这两个实现都有一个-i选项,它执行输入文件的原地更新[1],接受一个选项参数,指定要用于正在更新的文件的备份文件后缀名(文件扩展名)。

例如,在两个实现中,以下命令将原始文件file保留为备份文件file.bak

sed -i.bak 's/foo/bar/' file  # Keep original as 'file.bak'; NO SPACE between -i and .bak

尽管 使用 GNU sed 时,后缀参数是可选的, 而使用BSD/macOS sed时,它是强制性的, 但上述语法在两种实现中都可以工作,因为直接将选项参数(.bak)与选项(-i)相邻 - -i.bak,而不是 -i .bak - 既可以作为可选的选项参数,也可以作为强制性的选项参数。
  • 语法 -i.bak 是唯一适用于可选选项参数的形式。
  • 语法 -i.bak 也可以作为必选选项参数的形式,作为 -i .bak 的替代方案,即分别指定选项及其参数。

不指定后缀(通常是这种情况)意味着不保留备份文件,这就是不兼容性产生的原因

  • 使用 GNUsed,不指定后缀意味着仅使用 -i 本身

  • 使用 BSD/macOSsed,不指定后缀意味着将空字符串指定为必需的后缀,并且由于技术原因,空字符串只能作为单独的参数传递:即 -i '' 不是 -i''

-i'' 不起作用,因为对于 sed 来说,它与仅使用 -i 是无法区分的,因为 shell 实际上会删除空引号(它将 -i'' 连接并删除具有语法功能的引号),并在两种情况下都只传递 -i

只有指定了-i选项,才会将下一个参数解释为选项参数。
sed -i 's/foo/bar/' file # BREAKS with BSD/macOS Sed

's/foo/bar/' - 这是 Sed 脚本(命令)的格式,但现在被解释为后缀,而单词file则被解释为脚本。
将这样的单词解释为脚本可能会导致模糊的错误消息,例如
sed: 1: "file": invalid command code f,
因为f被解释为 Sed 命令(函数)。

类似地,使用以下内容:

sed -i -e 's/foo/bar/' file # CREATES BACKUP FILE 'file-e'

“-e”被解释为后缀参数,而不是Sed的“-e”选项(如果需要,可以用于指定多个命令)。因此,代替不保留备份,您会得到一个带有后缀“-e”的备份文件。
这个命令不能按预期工作不太明显,因为在满足后缀参数的语法要求的情况下,就地更新确实成功了。
意外创建这些备份文件很容易被忽视,这是Crt's incorrect answerthis incorrect answer to a similar question获得许多赞的最有可能的解释(截至本文撰写时)。
严格来说,临时文件会在后台创建,然后替换原始文件;这种方法可能存在问题:请参见我的 this answer 的下半部分。

7
这是回答问题的正确且最具信息量的答案。不确定谁给它点了踩,但没有任何理由表达为什么要踩这个答案,而我有很多理由认同这个答案比其他答案更好。 - mariop
我在这方面不是专家,但为了保持POSIX兼容性,mv file file.bak && sed 's/foo/bar/' file.bak > file && rm file.bak是否可行? - Cássio Renan
2
@CássioRenan:原则上是可以的,但是 sed 's/foo/bar' file > file.bak && mv file.bak file(如一个链接答案所示)更简单。如果 -i 可用且可移植性不是问题,则最好使用它,因为它会尽力保留原始文件的属性,尽管它仍然会破坏符号链接 - 请参见另一个链接答案的后半部分。符合 POSIX 标准的方法不仅也存在符号链接问题,而且只是创建了一个具有默认权限的新文件... - mklement0

24

人是你的朋友。

OS X

 -i extension
         Edit files in-place, saving backups with the specified extension.
         If a zero-length extension is given, no backup will be saved.  It
         is not recommended to give a zero-length extension when in-place
         editing files, as you risk corruption or partial content in situ-
         ations where disk space is exhausted, etc.

12

在OS X操作系统中,您可以使用GNU版本的sed:gsed

# if using brew
brew install gnu-sed

#if using ports
sudo port install gsed

然后,如果您的脚本需要可移植性,您可以根据您的操作系统定义要使用哪个命令。

SED=sed
unamestr=`uname`
if [[ "$unamestr" == "Darwin" ]] ; then
    SED=gsed
    type $SED >/dev/null 2>&1 || {
        echo >&2 "$SED it's not installed. Try: brew install gnu-sed" ;
        exit 1;
    }
fi
# here your sed command, e.g.:
$SED -i "/ $domain .*#drupalpro/d" /etc/hosts

2
这是救了我的一个。我升级了我的安装并将 /usr/local/opt/gnu-sed/libexec/gnubin 添加到了我的路径中。我使用的是OSX 10.12.6。 - Chris Sharp
这应该是被接受的解决方案。 - IulianT
对 GNU SED 依赖的处理很好,非常有帮助! - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接