sed:删除(输入的)最后一行会停止我的脚本

sed:删除(输入的)最后一行会停止我的脚本

我正在测试一个包含 2 个命令的sed脚本。第一个命令从输入中删除一行,第二个命令将一行附加到输入的末尾,因此:

$ cat input.txt 
Line 1
Line 2
Line 3

$ sed '/Line 1/d;$aAppended line' input.txt
Line 2
Line 3
Appended line

$ sed '/Line 2/d;$aAppended line' input.txt
Line 1
Line 3
Appended line

到目前为止,一切都很好。但是,如果我选择删除的行恰好是输入的最后一行,则脚本中的第二个命令永远不会运行:

$ sed '/Line 3/d;$aAppended line' input.txt
Line 1
Line 2

我了解到sed一次读取输入行,对每一行“执行”脚本,直到没有更多的行可供读取,因此我的脚本永远不会“完成”(即Appended line永远不会附加)这一事实说得通。

尽管如此,这对我来说是一个问题(我仍然需要追加Appended line)。有谁熟悉这个问题,如果是,是否有“标准解决方法”?我认为一种替代方法(删除最后一行然后附加一行)是代替:

$ sed 's/Line 3/Appended line/' file.txt
Line 1
Line 2
Appended line

这是我唯一的选择吗?

答案1

d手册页中的命令sed

删除模式空间。开始下一个周期。

由于多个sed命令按照提供的顺序执行,“开始下一个周期”实际上意味着在删除该行后,它停止执行可能适用于(当前周期的)已删除行的任何后续命令。因此,如果Line 3是最后一行,并且它被删除,则新的循环将开始,并且a待定应该应用于最后一行的命令将不会被执行。

为了解决这个问题,只需调换命令的顺序即可。

sed -e '$aAppended line' -e '/Line 3/d'

或者相当于 GNU 特定代码的标准:

sed '$a\
Appended line
/Line 3/d'

这样,文本将附加在最后一行之后 Line 3被删除(并且循环将结束)。

请注意,a不附加该行到模式空间;引用POSIX, 它安排稍后输出的文本,文本[...]应在下一次尝试获取输入行之前写入标准输出[...],所以它不受 的影响d,甚至不受该-n选项的影响。

答案2

使用-nsed 选项,您可以将问题从删除内容转变为打印内容。

#!/usr/bin/sed -nf
/Line 3/!p
$aAppended line

请注意,这/text/!是一个否定,即仅匹配不包含 的行text。并且p没有停止执行脚本其余部分的副作用,不会发生新的循环。

答案3

其他人已经解释了为什么会发生这种情况,所以我将提供一个不同的解决方案,使用perl,它没有这个问题,而不是sed

$ cat input.txt 
Line 1
Line 2
Line 3
$ perl -lne 'print unless /Line 3/; END{print "Appended line"}' input.txt 
Line 1
Line 2
Appended line

答案4

每当您发现自己使用 s、g 和 p(带 -n)以外的 sed 结构时,最好只使用 awk(另一个强制的 POSIX 文本处理工具),以获得清晰度、稳健性、可移植性的某种组合, ETC。

在每台 Unix 机器上的任何 shell 中使用任何 awk:

$ awk 'NR!=1; END{print "Appended line"}' input.txt
Line 2
Line 3
Appended line

$ awk 'NR!=2; END{print "Appended line"}' input.txt
Line 1
Line 3
Appended line

$ awk 'NR!=3; END{print "Appended line"}' input.txt
Line 1
Line 2
Appended line

相关内容