如果行匹配"foo",则删除行,上面的行匹配"bar",下面的行匹配"baz"?

bir*_*rac 4 awk sed

使用sed和/或awk,我希望只有当它包含字符串"foo"时才能删除一行,并且前后的行分别包含字符串"bar"和"baz".

所以对于这个输入:

blah
blah
foo
blah
bar
foo
baz
blah
Run Code Online (Sandbox Code Playgroud)

我们会删除第二个foo,但没有别的,留下:

blah
blah
foo
blah
bar
baz
blah
Run Code Online (Sandbox Code Playgroud)

我尝试使用while循环逐行读取文件,但这很慢,我无法弄清楚如何匹配上一行和下一行.

编辑 - 根据评论中的要求,这是我的while循环的当前状态.目前只匹配前一行(从前一个循环存储为$ linepre).

linepre=0 
while read line
do 
   if [ $line != foo ] && [ $linepre != bar ]
   then 
       echo $line
   fi
linepre=$line
done < foobarbaz.txt
Run Code Online (Sandbox Code Playgroud)

蛮丑的.

kva*_*our 5

有关优雅的perl解决方案,请参阅Sundeep的答案.

对于类似且非常好的sed解决方案,请参阅potong的第二个答案

两种解决方案都将文件完全读入内存并一次处理.如果您不需要处理GB文件大小,这很好.换句话说,这些是最好的解决方案(如果我们忽略CASE3).

评论:两种解决方案都失败了CASE3(见下文).CASE3是一个特别值得商榷的案例.


更新1:以下awk解决方案是一个适用于所有情况的新脚本.在特定情况下,此答案被接受的早期解决方案失败了.所提出的解决方案解决了嵌套分组(CASE3如下):

awk 'BEGIN{p=1;l1=l2=""}
     (NR>2) && p {print l1}
     { p=!(l1~/bar/&&l2~/foo/&&/baz/);
       l1=l2;l2=$0
     }
     END{if (l1!="" && p) print l1
         if (l2!=""     ) print l2}' <file>
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我们不断缓冲存储在中的3行l1,l2$0.每次处理新行时,我们确定是否l1应该在下一个周期中打印并交换缓冲行.打印仅从NR=3前进开始.要打印的条件是l1包含bar,l2包含foo$0包含baz,那么我们不会在下一个循环中打印.

更新2:sed基于相同的原理溶液可制得.sed有两个回忆.该模式空间是你做的所有操作和保持空间是一个长期的记忆.我们的想法是将这个词print放在保留空间中,但我们只能通过交换空间(使用x)来做到这一点

 sed '1{x;s/^.*$/print/;x;N};                           #1
      N;                                                #2
      x;/print/{z;x;P;x};x;                             #3
      /bar.*\n.*foo.*\n.*baz/!{x;s/^.*$/print/;x};      #4
      $s/\(bar.*\)\n.*foo.*\n\(.*baz\)/\1\n\2/;         #5
      D' <file>                                         #6
Run Code Online (Sandbox Code Playgroud)
  • line #1通过将单词print放在hold空间(x;s...;x)中来初始化状态,并将另一行附加到模式空间(N)
  • line #2将第三行添加到模式空间
  • line #3确定我们是否需要通过检查保留空间来打印模式空间的第一行,并删除保留空间P打印到\n模式空间中的第一行并且z压缩模式空间
  • line #4确定我们是否应该在下一个周期打印.检查实际模式是否匹配,如果没有将单词print放在保留空间中
  • line #5,是文件结束条件
  • line #6删除\n模式空间中的第一行,然后返回到#1不读新行.

退出时,再次打印图案空间.

注释:如果要查看模式空间和保持空间的外观,可以在每行后添加以下代码:s/^/P:/;l;s/^P://;x;s/^/H:/;l;s/^H://;x.该行将P:分别H:在前面打印两个空格.

二手测试文件:

# bar-foo-baz test file
# An asterisk indicates the foo
# lines that should be removed
<CASE0 :: default case>
bar
foo (*)
baz
<CASE1 :: reset cycle on second line>
bar
foobar
foo (*)
baz
<CASE2 :: start cycle at end of previous cycle>
bar
foo (*)
bazbar
foo (*)
baz
<CASE3 :: nested cases>
bar
foobar (*)
foobaz (*)
baz
<CASE4 :: end-of-file case>
bar
foo
Run Code Online (Sandbox Code Playgroud)

以前接受的答案:(更新以指示哪些案例失败)

awk:失败CASE3

awk '!/baz/&&(c==2){print foo}
     /bar/         {c=1;print;next}
     /foo/ &&(c==1){c++;foo=$0;next}
                   {c=0;print}
     END{if(c==2){print foo}}' <file>
Run Code Online (Sandbox Code Playgroud)

此解决方案默认打印所有行,除非行包含在包含foo行之后的行bar.上面的逻辑决定了我们是否应该打印线foo.

  • !/baz/&&(c==2){print foo}:这解决了提前终止.如果baz在有效bar-foo组合后找不到,则打印该foo行.

  • /bar/{c=1;print;next}:这初始化了一个新周期的开始.如果bar找到,请设置c1,打印该行并移至下一行.bar线总是打印出来.此行解析CASE1CASE2.

  • /foo/&&(c==1){c++;foo=$0;next}:这会检查bar-foo组合.它存储该foo行并移动到下一行.

  • {c=0;print},如果我们达到这一点,则意味着我们没有找到一条bar线或一个bar-foo组合.只需默认打印该行并将计数器重置为零.

  • END{if(c==2){print foo}} 这句话刚刚解决了 CASE4

gawk:失败CASE3

awk 'BEGIN{ORS="";RS="bar[^\n]*\n[^\n]*foo[^\n]*\n[^\n]*baz"}
     {sub(/\n[^\n]*foo[^\n]*\n/,"\n",RT); print $0 RT}' <file>
Run Code Online (Sandbox Code Playgroud)

RS被设置为bar[^\n]*\n[^\n]*foo[^\n]*\n[^\n]*baz,即,我们感兴趣的图案.在此,[^\n]*\n[^\n]*表示含有一个单一的一个字符串\n,因此RS代表有效bar-foo-baz组合.RT编辑找到的记录分隔符sub以删除该foo行并在找到的记录后打印.

RT(gawk扩展名)RS与记录分隔符所表示的文本匹配的输入文本 .每次读取记录时都会设置它.

sed:失败CASE1, CASE2, CASE3, CASE4

sed -n '/bar/{N;/\n.*foo/{N;/foo.*\n.*baz[^\n]*$/{s/\n.*foo.*\n/\n/}}};p' <file>
Run Code Online (Sandbox Code Playgroud)
  • /bar/{N;...}如果该行包含bar,则将下一行追加到模式缓冲区(N)
  • /\n.*foo/{N;...}如果图案缓冲器具有foo一个换行字符之后,追加下一行的图案缓冲器(N)
  • /foo.*\n.*baz[^\n]*$/{s/\n.*foo.*\n/\n/}如果模式缓冲区包含foo后跟单个换行符并以包含的行结束baz,则删除包含的行foo.这里的搜索模式将案例排除在外barfoo\nfoobaz\ncar

  • @Sundeep你是对的......我已经纠正过了! (2认同)
  • 按要求@Sundeep,添加评论和另一个`sed`解决方案. (2认同)