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


40

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

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

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

sed在Linux上可以与GNU一起使用,但是FreeBSD sed希望-ioption 后的第一个参数是备份副本的扩展名。它是这样的:

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

但是,GNU sed依次期望表达式在之后-i。(它也需要使用换行符进行修复,但这已在此处得到解答)

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


您提供的两个sed片段相同,您确定没有错字吗?另外,我能够执行GNU sed的供应权后,备份扩展-i
iruvar

杜,谢谢你发现这个问题。我已经解决了我的问题。第二行导致我的sed错误,它期望'1s / ^ / <!DOCTYPE html> \ n /'是一个文件,并抱怨找不到它。
红色

1
交叉引用:sed就位标志,在Mac(BSD)和Linux on Stack Overflow上均可使用
MvG

Answers:


39

GNU sed在之后接受可选扩展-i。扩展名必须在同一参数中,且中间没有空格。此语法也适用于BSD sed。

sed -i.bak -e '…' SOMEFILE

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

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

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
GNU sed -i也暗示着-s。检查GNU的最简单方法sed是使用该sed v命令,该命令对于GNU是有效的noop,但在其他地方均失败。
mikeserv 2014年

通过上面的提示启发,这里是一个单行(如果丑陋的),对于那些谁真的想要一个便携版本,但它确实产卵子shell:sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"如果不是GNU sed的,它后插入一个空格,然后由两个燎引号-i,以便它在BSD上工作。GNU sed只得到-i
伊万X

2
@IvanX我对使用v命令的存在来测试GNU sed 持谨慎态度。如果FreeBSD决定实施该怎么办?
吉尔斯(Gilles)'所以

@Gilles公平的观点,但是GNU sed的手册页将v描述为恰好用于此目的(检测到它是GNU sed而不是其他东西),因此有人希望* BSD能够做到这一点。除了使用-i本身以外,我想不出另一项测试,该测试不会对GNU sed采取任何措施,而会导致BSD sed出错(反之亦然),但是这需要首先创建一个虚拟文件。您对上面的sed的测试还可以,但是对于内联而言却很难。正如您所建议的那样,完全避免-i无疑是最安全的选择,但是我可以使用,sed v因为这是现有的目的。
伊万X

1
@lapo一种替代方法是定义一个函数,请参见我的编辑。
吉尔斯(Gilles)'“ SO-别邪恶”

10

如果您没有找到使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}"

8

您始终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

我最终求助于Perl,但是使用ed是一个不错的选择,它应该在Unix类用户中不受欢迎。
红色

@Red-很高兴听到您解决了您的问题。是的,我以前没有看到过,谷歌搜索了它,实际上这似乎是最便捷,最合适的方法。
slm

7

您还可以手动perl -i执行幕后操作:

{ rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file

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

拥有:

sed '1i\
<!DOCTYPE html>' file 1<> file

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

基本上sed可以在4k的块上进行读写。如果要插入的行小于4k,sed将永远不会覆盖尚未读取的块。

我不会指望它。


应该echo '<!DOCTYPE html>'或不带“”引号引起来。

@AD好点。我倾向于忘记交互式bash / zsh的bug ^ W功能,因为我通常会自己禁用它。
斯特凡Chazelas

这是一个非常这里几个答案不具有重定向到一个“临时文件”用一个静态的,可预见的名字的一个全开放的安全漏洞,不检查,如果它已经存在。我相信这应该是公认的答案。也非常好地演示了组命令的用法。
通配符

3

也在Mac OS X上使用的FreeBSD sed,需要-e-i切换后正确正确地定义和识别以下(regex)命令的选项。

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

更一般而言,在FreeBSD之后省略备份扩展名sed -i需要一些显式sed选项或在后面-i进行切换,以避免FreeBSD sed在解析其命令行参数时混淆其一部分。

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

(作为一般提示,FreeBSD的最新版本进行sed-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

5
不,bsdsed -i -e 's/a/A/'不是就地编辑,它与保存原始与“-E”后缀编辑(testfile.txt-e)。
斯特凡Chazelas

注意,为了与FreeBSD兼容,GNU sed还支持-E(除了-r之外)。-E可能在下一个POSIX版本中指定,因此我们都应该忘记-r无意义,并假装它根本不存在。
斯特凡Chazelas

2

为了sed -i可移植地模拟单个文件,同时尽可能避免出现竞争情况:

sed 'script' <<FILE >file
$(cat file)
FILE

顺便说一句,这还解决了可能sed -i引入的问题,根据目录和文件权限,该问题sed -i可能使用户可以覆盖该用户没有编辑权限的文件。

您可能还会执行以下备份:

sed 'script' <<FILE >file
$(tee file.old <file)
FILE

2

您可以在Ex模式下使用Vim:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 选择第一行

  2. i 插入文字和换行符

  3. x 保存并关闭

甚至如评论中那样,是简单的旧标准 ex

printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file 

这里没有特定于Vim的东西。这完全符合的POSIX规范ex,除了不需要支持多个-c标志的实现。为了获得明确的可移植性,我将使用printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
通配符
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.