如何使用 sed -i (就地编辑)实现可移植性?

如何使用 sed -i (就地编辑)实现可移植性?

我正在为我的服务器编写 shell 脚本,该服务器是运行 FreeBSD 的共享主机。我还希望能够在运行 Linux 的 PC 上本地测试它们。因此,我试图以可移植的方式编写它们,但sed我认为没有办法做到这一点。

我的网站的一部分使用生成的静态 HTML 文件,并且此 sed 行在每次重新生成后插入正确的 DOCTYPE:

sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

它可以与 Linux 上的 GNU 配合使用sed,但 FreeBSDsed期望选项后的第一个参数-i是备份副本的扩展名。它看起来是这样的:

sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

然而,GNUsed反过来期望表达式紧跟在 后面-i。 (它还需要修复换行处理,但这已经在这里

当然,我可以在脚本的服务器副本中包含此更改,但这会弄乱我使用 VCS 进行版本控制的情况。有没有办法用 sed 以完全可移植的方式实现这一点?

答案1

GNU sed 接受-i.扩展名必须位于同一参数中,中间不得有空格。此语法也适用于 FreeBSD sed。

sed -i.bak -e '…' SOMEFILE

请注意,在 FreeBSD 上,-i当存在多个输入文件时,也会更改行为:它们是独立处理的(例如,$匹配每个文件的最后一行)。而且这在 BusyBox 上不起作用。

如果您不想使用备份文件,您可以检查可用的 sed 版本。

# Assume that sed is either FreeBSD/macOS or GNU
case $(sed --help 2>&1) in
  *GNU*) set sed -i;;
  *) set sed -i '';;
esac
"$@" -e '…' "$file"

或者,为了避免破坏位置参数,请定义一个函数。

case $(sed --help 2>&1) in
  *GNU*) sed_i () { sed -i "$@"; };;
  *) sed_i () { sed -i '' "$@"; };;
esac
sed_i -e '…' "$file"

如果您不想打扰,请使用 Perl。

perl -i -pe '…' "$file"

如果您想编写可移植脚本,请不要使用-i— 它不在 POSIX 中。手动执行 sed 在后台执行的操作 — 只需多一行代码。

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"

答案2

如果你找不到让sed游戏变得更好的技巧,你可以尝试:

  1. 不要使用-i

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
    
  2. 使用 Perl

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"
    

答案3

编辑

您始终可以使用ed在现有文件前面添加一行。

$ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html

细节

周围的位<!DOCTYPE html>是指示ed它将该行添加到文件的命令my.html

sed

我相信这个命令sed也可以使用:

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv

答案4

自由BSDsed也在 Mac OS X 上使用,需要切换-e后的选项-i来正确且明确地定义和识别以下(正则表达式)命令。

换句话说,sed -i -e ...应该与 FreeBSD 和 GNU 一起使用sed

更一般地,在 FreeBSD 之后省略备份扩展sed -i需要一些明确的sed选项或开关,以避免在解析其命令行参数时-iFreeBSD 部分出现混乱。sed

(但是请注意,就地sed文件编辑会导致文件 inode 更改,请参阅“就地”编辑文件)。

(作为一般提示,最新版本的 FreeBSDsed-r切换以增加与 GNU 的兼容性sed)。

echo a > testfile.txt
ls -li testfile.txt
#gsed -i -e 's/a/A/' testfile.txt
#bsdsed -i 's/a/A/' testfile.txt  # does not work
bsdsed -i -e 's/a/A/' testfile.txt
ls -li testfile.txt
cat testfile.txt

相关内容