我正在测试一个包含 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
使用-n
sed 选项,您可以将问题从删除内容转变为打印内容。
#!/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