【问题标题】:awk script: removing line previous to pattern match and after, until a blank lineawk 脚本:删除模式匹配之前和之后的行,直到空行
【发布时间】:2014-10-07 10:33:06
【问题描述】:

我昨天开始学习 awk,试图解决这个问题(并学习一种有用的新语言)。起初我尝试使用 sed,但很快意识到它不是访问/操作模式匹配之前的行的正确工具。

我需要:

  1. 删除所有包含“foo”的行(它本身很简单,但在跟踪之前的行时不是这样)
  2. 查找包含“bar”的行
  3. 删除包含“bar”的行之前的行
  4. 删除包含“bar”的行之后的所有行,直到我们到达一个空行

示例输入:

This is foo stuff
I like food!
It is tasty!

stuff
something
stuff
stuff
This is bar
Hello everybody
I'm Dr. Nick

things
things
things

期望的输出:

It is tasty!

stuff
something
stuff

things
things
things

我的尝试:

{
    valid=1;             #boolean variable to keep track if x is valid and should be printed
    if ($x ~ /foo/){     #x is valid unless it contains foo 
        valid=0;         #invalidate x so that is doesn't get printed at the end
        next;
    }
    if ($0 ~ /bar/){     #if the current line contains bar
        valid = 0;       #x is invalid (don't print the previous line)
        while (NF == 0){ #don't print until we reach an empty line
            next;
        }
    }
    if (valid == 1){     #x was a valid line
        print x;                        
    }
    x=$0;                #x is a reference to the previous line
}

超级奖励积分(不需要解决我的问题,但我有兴趣了解如何完成):

  1. 能够在模式匹配之前删除 n 行
  2. 在输出中包含/排除空行的选项

【问题讨论】:

    标签: bash awk gawk


    【解决方案1】:

    一种方式:

    awk '
          /foo/ { next }     
     flag && NF { next }     
    flag && !NF { flag = 0 }      
          /bar/ { delete line[NR-1]; idx-=1; flag = 1; next } 
                { line[++idx] = $0 }
    END {
        for (x=1; x<=idx; x++) print line[x]
    }' file
    It is tasty!
    
    stuff
    something
    stuff
    
    things
    things
    things
    
    • 如果行包含foo,则跳过它。
    • 如果启用标志并且行不为空白,则跳过它。
    • 如果启用标志并且行为空白,则禁用标志。
    • 如果行包含bar 删除上一行,重置计数器,启用标志并跳过它
    • 将通过管理的所有行存储在以递增编号为索引的数组中
    • END 块中打印行。

    旁注:

    • 要在模式匹配之前删除n 行数,您可以创建一个循环。从当前行号开始并使用反向 for 循环,您可以从临时缓存(数组)中删除行。然后你可以从你自定义的计数器变量中减去n

    • 要包含或排除空行,​​您可以使用NF 变量。对于典型的行,NF 变量设置为基于字段分隔符的字段数。对于空行,此变量为 0。例如,如果您在上面的答案中将 END 块上方的行修改为 NF { line[++idx] = $0 },您将看到我们已绕过输出中的所有空行。

    【讨论】:

      【解决方案2】:

      这个 awk 应该可以在不将完整文件存储在内存中的情况下工作:

      awk '/bar/{skip=1;next} skip && p~/^$/ {skip=0} NR>1 && !skip && !(p~/foo/){print p} {p=$0} 
          END{if (!skip && !(p~/foo/)) print p}' file
      
      It is tasty!
      
      stuff
      something
      stuff
      
      things
      things
      things
      

      【讨论】:

        【解决方案3】:

        下面是另一个 awk 脚本,它使用模式和函数来触发状态更改和管理输出,产生相同的结果。

        function show_last() {
          if (!skip && !empty) {
            print last
          }
          last = $0
          empty = 0
        }
        function set_skip_empty(n) {
          skip = n
          last = $0
          empty = NR <= 0
        }
        BEGIN  { set_skip_empty(0)        }
        END    { show_last() ;            }
        /foo/  { next;                    }
        /bar/  { set_skip_empty(1) ; next }
        /^ *$/ { if (skip > 0) { set_skip_empty(0); next } else show_last() }
        !/^ *$/{ if (skip > 0) { next }                    else show_last() }
        

        这可以通过在变量last 中保留“当前”行来实现,即 忽略或输出,取决于其他事件,例如foobar的发生。

        empty 变量跟踪 last 变量是否真的是 一个空行,或者从一开始就为空(例如,BEGIN)。

        要完成“奖励积分”,请将last 替换为行数组,然后可以根据需要累积N 行数。

        要排除空行(例如终止bar 过滤器的行),请将empty 测试替换为对last 变量长度的测试。在awk 中,空行没有长度(但是,带有空格或制表符的行*do* 有长度)

        function show_last() {
          if (!skip && length(last) > 0) {
            print last
          }
          last = $0
        }
        

        将导致没有空行输出。

        【讨论】:

          【解决方案4】:

          将每个空行分隔的段落作为字符串读取,然后执行 gsub() 删除与您关心的模式的 RE 匹配的字符串:

          $ awk -v RS= -v ORS="\n\n" '{ gsub(/[^\n]*foo[^\n]*\n|\n[^\n]*\n[^\n]*bar.*/,"") }1' file
          It is tasty!
          
          stuff
          something
          stuff
          
          things
          things
          things
          

          要删除 N 行,请将 [^\n]*\n 更改为 ([^\n]*\n){N}

          要不删除部分 RE,请使用 GNU awk 并使用 gensub() 而不是 gsub()

          要删除空白行,请更改ORS 的值。

          和它一起玩......

          【讨论】:

          • 我的test results 中的变化也很奇怪(针对@anubhava 的解决方案进行了测试)。有趣的是,BSDawk 使用@EdMorton 的方法甚至比mawk 更快。
          • 在我看来,你们俩都看到了缓存的结果,使您的第二次运行更快。您需要运行一个脚本至少 2 到 3 次,然后才能获取时间信息,以便将苹果与苹果进行比较。
          • 好点,我只是跑了几次,得到了非常相似的结果。
          • 然后打败我。听起来对 OP 来说是一个很好的调查机会:-)。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-04
          • 2021-06-16
          • 1970-01-01
          • 2022-01-16
          • 1970-01-01
          相关资源
          最近更新 更多