假设我有以下输入。
Header
thing0 some info
thing4 some info
thing4 some info
thing4 some info
thing2 some info
thing2 some info
thing3 some info
现在,我希望能够像这样在“thing4”的最后一次成功匹配上附加“foo”。
Header
thing0 some info
thing4 some info
thing4 some info
thing4 some info
foo
thing2 some info
thing2 some info
thing3 some info
不一定保证顺序,但此示例中的顺序编号只是为了表明在某些文本行之前有一个可搜索的关键字,并且它们在输入时通常组合在一起,但不能保证。
使用单个 awk 你可以这样做:
awk 'FNR==NR{ if (/thing4/) p=NR; next} 1; FNR==p{ print "foo" }' file file
Header
thing0 some info
thing4 some info
thing4 some info
thing4 some info
foo
thing2 some info
thing2 some info
thing3 some info
早期解决方案:您可以使用
tac + awk + tac
:
tac file | awk '!p && /thing4/{print "foo"; p=1} 1' | tac
这可能对你有用(GNU sed):
sed '1h;1!H;$!d;x;s/.*thing4[^\n]*/&\nfoo/' file
将文件放入内存并使用正则表达式的贪婪将所需的字符串放置在所需模式的最后一次出现之后。
更高效(使用最少的内存)但更难理解的是:
sed '/thing4[^\n]*/,$!b;//{x;//p;g};//!H;$!d;x;s//&\nfoo/' file
这个解释留给读者去思考。
sed -e "$(grep -n 'thing4' file |tail -1|cut -f1 -d':')a foo" file
使用 shell 和 grep 获取包含该模式的最后一个行号,然后使用该数字作为 sed append 命令的地址。
啊,我找到了 here 在堆栈上。补充了 @anubhava 的解决方案,该解决方案利用
tac
翻转追加,然后再次翻转,创建在最后一次出现时追加的错觉。感谢您的帮助。
tac | sed '0,/thing4/s/thing4/foo\n&/' | tac
它可以像
一样简单awk 'BEGIN{RS="^$"}
{$0=gensub(/(.*thing4[^\n]*\n)/,"\\1foo\n","1",$0);printf "%s",$0}' file
示例输入
Header
thing0 some info
thing4 some info
thing4 some info
thing4 some info
thing2 some info
thing2 some info
thing3 some info
示例输出
Header
thing0 some info
thing4 some info
thing4 some info
thing4 some info
foo
thing2 some info
thing2 some info
thing3 some info
这里发生了什么
我们将记录分隔符RS设置为空,即
^$
,我们将整个文件视为一条记录。.*thing4[^\n]*\n
匹配任何内容,直到包含 thing4
的最后一行。gensub 允许通过特殊调整来重复使用第一个匹配的模式
\1
。由于替换是一个字符串,我们需要添加一个额外的\
,这样整个替换就变成了\\1foo\n
。 \n
确实是一个转义序列,所以我们不需要在n
之前放置两个反斜杠。 注释
尚不完全清楚这些行是否总是按关键字分组。如果是这样,那么这个单一的
awk
方法也应该有效:
awk -v s=thing3 -v t=foo 'END{if(f) print t} {if($0~s)f=1; else if(f) {print t; f=0}}1' file
或:
awk -v s=thing0 -v t=foo 'END{if(f)print t} {f=$0~s} f,!f{if(!f)print t}1' file
这对我有用:
sed -i '/thing4/afoo' file