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

Red*_*Red 51 shell freebsd sed gnu portability

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

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

sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html}
Run Code Online (Sandbox Code Playgroud)

它适用sed于 Linux上的 GNU ,但 FreeBSDsed期望-i选项后的第一个参数是备份副本的扩展名。这是它的样子:

sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html}
Run Code Online (Sandbox Code Playgroud)

但是,GNUsed反过来又希望表达式紧跟在 之后-i。(它还需要修复换行符处理,但这里已经回答了)

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

Gil*_*il' 54

GNU sed 在-i. 扩展名必须在同一个参数中,中间没有空格。此语法也适用于 BSD sed。

sed -i.bak -e '…' SOMEFILE
Run Code Online (Sandbox Code Playgroud)

请注意,在 BSD 上,-i当有多个输入文件时也会改变行为:它们是独立处理的(例如$匹配每个文件的最后一行)。这也不适用于 BusyBox。

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

case $(sed --help 2>&1) in
  *GNU*) set sed -i;;
  *) set sed -i '';;
esac
"$@" -e '…' "$file"
Run Code Online (Sandbox Code Playgroud)

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

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

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

perl -i -pe '…' "$file"
Run Code Online (Sandbox Code Playgroud)

如果你想写一个可移植的脚本,不要使用-i——它不在 POSIX 中。手动执行 sed 在幕后所做的事情——它只是多一行代码。

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"
Run Code Online (Sandbox Code Playgroud)

  • GNU `sed -i` 也意味着 `-s`。检查 GNU `sed` 的最简单方法是使用 `sed v` 命令,它是 GNU 的有效 noop,但在其他任何地方都失败。 (2认同)
  • @IvanX 我对使用 `v` 命令的存在来测试 GNU sed 持谨慎态度。如果 FreeBSD 决定实施它会怎样? (2认同)

ter*_*don 13

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

  1. 不要使用-i

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用 Perl

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"
    
    Run Code Online (Sandbox Code Playgroud)


slm*_*slm 9

编辑

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

$ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html
Run Code Online (Sandbox Code Playgroud)

细节

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

sed

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

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv
Run Code Online (Sandbox Code Playgroud)


Sté*_*las 8

您还可以手动perl -i执行引擎盖下的操作:

{ rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file
Run Code Online (Sandbox Code Playgroud)

perl -i,没有备份,并且像这里给出的大多数解决方案一样,请注意它可能会影响文件的权限、所有权,并且可能会将符号链接转换为常规文件。

和:

sed '1i\
<!DOCTYPE html>' file 1<> file
Run Code Online (Sandbox Code Playgroud)

sed会覆盖文件本身,因此不会影响所有权和权限或符号链接。它适用于 GNU,sed因为在用命令覆盖它之前,sed通常会从file(在我的情况下为 4k)读取一个充满数据的缓冲区i。如果文件超过 4k,那将不起作用,除了sed它还缓冲其输出的事实。

基本上sed适用于 4k 块进行读写。如果要插入的行小于 4k,sed则永远不会覆盖它尚未读取的块。

不过我不会指望它。