Regex 最后一次与sed匹配后追加行

Regex 最后一次与sed匹配后追加行,regex,bash,sed,Regex,Bash,Sed,假设我有以下输入 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 fo

假设我有以下输入

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
啊,我找到了 在堆栈上。以@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
样本输出

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设置为null,即
    ^$
    ,我们将整个文件视为一条记录

  • gensub中的
    *thing4[^\n]*\n
    匹配任何内容,直到包含
    thing4
    的最后一行

  • gensub允许通过特殊调整重新使用第一个匹配的图案
    \1
    。由于替换是一个字符串,我们需要添加一个额外的
    \
    ,因此整个替换变成
    \\1foo\n
    \n
    确实是一个转义序列,因此我们不需要在
    n
    之前放置两个向后斜杠


  • 注释

  • 解决方案是特定于GNUAWK的,但也可以轻松地对其他版本进行调整
  • 因为整个文件都应该读到内存中,所以这个解决方案最适合小文件,仍然是跨越数兆字节的nbd文件

  • 不完全清楚这些行是否总是按关键字分组。如果是这样,那么这种单一的
    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
    
    或:

    这可能适用于您(GNU-sed):

    将文件拖到内存中,并使用regexp的语法将所需字符串放在所需模式的最后一次出现之后

    更高效(使用最少内存)但更难理解的是:

    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命令的地址

    如果保证对输入进行排序,您可能可以在核心不使用POSIX兼容的
    awk
    命令的情况下执行此操作,但请注意
    tac
    是GNU的专用工具;在BSD/OSX平台上,您可以使用
    tail-r
    ;POSIX既不规定也不允许。您的解决方案现在是唯一一个不依赖所有匹配项分组的POSIX兼容解决方案。应该注意的是,这实际上不会更改文件,而是将修改后的内容打印到stdout。可以使用GNU awk就地更改文件:
    gawk-i inplace'FNR==NR{if(/thing4/)p=NR;next}1;FNR==p{print“foo”}文件
    如果需要幂等性:
    grep-Fxq'foo'文件| | awk'FNR==NR{If(/thing4/)p=NR;next}1;FNR==p{print“foo”}文件
    (也许有办法单独使用这个awk吗?)这非常相似,但并不比我建议的基于awk的解决方案好,因为它不适用于非gnu sedTwo
    tac
    s在这里很昂贵。您可以不用他们使用
    sed
    awk
    好问题,但在谈到
    sed
    awk
    问题时,最好提及您的平台,因为这些实用程序的GNU实现有许多在非Linux平台上无法工作的特性(默认情况下);举个例子:您自己的答案需要GNU实用程序。此外,请避免使用诸如“一般分组”之类的模糊短语,因为这会使分组是否可靠变得不明确;请注意,解决方案是GNU
    awk
    特定的;另外,值得一提的是,整个文件作为一个整体读入内存,使得此解决方案仅适用于较小的文件;最好用
    “1”
    (甚至
    )替换
    “g”
    ,这样就不会错误地建议进行多次替换。这是一个很好的技巧,但仅当搜索的模式
    thing4
    在连续行上时才有效。嗨,anubhava,是的,这就是我所说的“按关键字分组”。示例和文本都表明可能是这种情况(文本对我来说有点模糊)…您好,我知道有点晚了,但是如何插入变量而不是
    foo
    ?我尝试了
    sed'/thing4[^\n]*/,$!b//{x;//p;g};//!H、 美元!Dx;s/&\n'${foo}'/'文件
    ,但它不起作用,因为我的变量中包含
    /
    。有什么建议吗?@Some53创建一个新变量,每个变量的前缀都是\。但是请记住,
    /
    可能不是唯一需要在原始变量中添加前缀的字符,例如,
    *
    ^
    $
    [
    ]
    &
    和\。或者将替换分隔符改为
    #