【问题标题】:search (e.g. awk, grep, sed) for string, then look for X lines above and another string below搜索(例如 awk、grep、sed)字符串,然后查找上面的 X 行和下面的另一个字符串
【发布时间】:2013-05-17 15:56:40
【问题描述】:

我需要能够搜索字符串(让我们使用 4320101),在字符串上方打印 20 行并在此之后打印,直到找到字符串

例如:

Random text I do not want or blank line
16 Apr 2013 00:14:15
id="4320101"
</eventUpdate>
Random text I do not want or blank line

我只想将以下结果输出到文件中:

16 Apr 2013 00:14:15
id="4320101"
</eventUpdate>

在我想要的文件中有多个这些文本组的示例。

我尝试在下面使用这个:

cat filename | grep "</eventUpdate>" -A 20 4320101 -B 100 > greptest.txt

但它只显示字符串两侧的 20 行。

注意事项:
- 文本所在的行号不一致,所以我不能关闭这些,因此我使用 -A 20。
- 理想情况下,我宁愿拥有它,这样当它搜索字符串时,它会在找到时停止然后继续搜索。

总结:找到4320101,输出4320101以上的20行(或一行空白),然后输出4320101以下的所有行直到

</eventUpdate>

做研究我不确定如何让 awk、nawk 或 sed 为我工作。

【问题讨论】:

  • -A 是 grep 将在匹配行之后打印A的行数。如果您不想要匹配行之后的任何行,为什么要要求 20 行?另外我不明白你关于行号不一致的评论,但如果你想要 20 行 Before,请使用 -B 20
  • 我明白了。这应该有效: cat 文件名 | grep 4320101 -A 100 -B 20 ,但它返回: : No such file or directory
  • 我意识到 -A 和 -B 的工作方式是它需要值文件名,因此它认为 eventUpdate 是一个文件。但是我只想搜索到 eventUpdate(给它 100 行以在 4320101 以下找到它)
  • 没有标准的工具来完成这项工作。我有一个 70 行(非最小)的 Perl 脚本,它在前后各行;它必须修改为处理“直到替代模式之后”,但这比“从 4320101 之前的指定模式”要容易得多。我还有一个在每个文件上使用ed(以及sedsortsed)的shell 脚本版本——这将是最容易适应的。如果您需要这些,请与我联系(请参阅我的个人资料);它们不是您问题的完全有效的解决方案,但可能是提供问题的有用垫脚石。
  • 如果 2 个范围重叠,您希望发生什么,例如如果 2 次出现 4320101 位于彼此相距 20 行之内?打印所有行一次?将两个范围内的行打印两次?

标签: bash sed awk grep nawk


【解决方案1】:

这可能对你有用(GNU sed):

sed ':a;s/\n/&/20;tb;$!{N;ba};:b;/4320102/!D;:c;n;/<\/eventUpdate>/!bc' file

编辑:

  • :a;s/\n/&amp;/20;tb;$!{N;ba}; 这会在模式空间 (PS) 中保留一个 20 行的窗口
  • :b;/4320102!D; 这将在文件中移动上述窗口,直到找到模式 4320102
  • :c;n;/&lt;\/eventUpdate&gt;/!bc 打印 20 行窗口和任何后续行,直到找到模式 &lt;\/eventUpdate&gt;

【讨论】:

  • 这非常有效。只是将其更改为检查 3 行,从读取 ID(在本例中为 4320102)读取用户输入,然后执行 /'$ID' 。不过我不得不问,你能分解一下所有这些到底是做什么的吗?
【解决方案2】:

这是一个丑陋的awk 解决方案:)

awk 'BEGIN{last=1}
{if((length($0)==0) || (Random ~ $0))last=NR} 
/4320101/{flag=1;
if((NR-last)>20) last=NR-20;
cmd="sed -n \""last+1","NR-1"p \" input.txt";
system(cmd);
}
flag==1{print}
/eventUpdate/{flag=0}' <filename>

所以基本上它的作用是跟踪last 变量中包含Random 模式的最后一个空白行或行。现在,如果找到了4320101,它将通过system sed 命令从更接近的that line -20 or last 打印。并设置flagflag 导致打印下一行,直到找到 eventUpdate。虽然没有测试,但应该可以工作

【讨论】:

    【解决方案3】:

    在 sed/awk 中向后看总是很棘手。这个自包含的 awk 脚本基本上保存最后 20 行,当它到达 4320101 时,它会打印这些存储的行,直到空白处或找到不需要的线路,然后停止。此时它会切换到printall 模式并打印所有行,直到遇到eventUpdate,然后打印并退出。

    awk '
    function store( line ) {
        for( i=0; i <= 20; i++ ) {
            last[i-1] = last[i]; i++;
        };
        last[20]=line;
    };
    function purge() {
        for( i=20; i >= 0; i-- ) {
            if( length(last[i])==0 || last[i] ~ "Random" ) {
                stop=i;
                break
            };
        };
        for( i=(stop+1); i <= 20; i++ ) {
            print last[i];
        };
    
    };
    {
    store($0);
    if( /4320101/ ) {
        purge();
        printall=1;
        next;
    };
    if( printall == 1) {
        print;
        if( /eventUpdate/ ) {
            exit 0;
        };
    };
    }' test
    

    【讨论】:

      【解决方案4】:

      让我们看看我是否理解您的要求:

      您有两个字符串,我将其命名为KEYLIMIT。而你想打印:

      1. 在包含 KEY 的行之前最多 20 行,但如果有空行则停止。

      2. 包含KEY 的行和包含LIMIT 的下一行之间的所有行。 (这忽略了您要求不超过 100 行这样的行;如果这很重要,添加起来相对简单。)

      完成(1) 的最简单方法是保留一个20 行的循环缓冲区,并在您点击key 时将其打印出来。 (2) 在 sed 或 awk 中都是微不足道的,因为您可以使用双地址形式来打印范围。

      那么让我们在 awk 中做吧:

      #file: extract.awk
      
      # Initialize the circular buffer
      BEGIN          { count = 0; }
      # When we hit an empty line, clear the circular buffer
      length() == 0  { count = 0; next; }
      # When we hit `key`, print and clear the circular buffer
      index($0, KEY) { for (i = count < 20 ? 0 : count - 20; i < count; ++i)
                         print buf[i % 20];
                       hi = 0;
                     }
      # While we're between key and limit, print the line
      index($0, KEY),index($0, LIMIT)
                     { print; next; }
      # Otherwise, save the line
                     { buf[count++ % 20] = $0; }
      

      为了让它工作,我们需要设置KEYLIMIT 的值。我们可以在命令行上这样做:

      awk -v "KEY=4320101" -v "LIMIT=</eventUpdate>" -f extract.awk $FILENAME
      

      注意事项:

      1. 我使用了index($0, foo) 而不是更常见的/foo/,因为它避免了必须转义正则表达式特殊字符,而且甚至不需要正则表达式的要求。 index(haystack, needle)haystack 中返回needle 的索引,索引从1 开始,如果没有找到needle,则返回0。用作真/假值,找到needle为真。

      2. next 导致当前行的处理结束。正如这个小程序所示,它非常方便。

      【讨论】:

        【解决方案5】:

        你可以试试这样的 -

        awk '{ 
            a[NR] = $0
        }
        
        /<\/eventUpdate>/ { 
            x = NR
        }
        
        END {
            for (i in a) {
                if (a[i]~/4320101/) {
                    for (j=i-20;j<=x;j++) {
                    print a[j]
                    }
                }
            }
        }' file
        

        【讨论】:

          【解决方案6】:

          最简单的方法是使用文件的 2 次传递 - 第一次识别目标正则表达式所在范围内的行号,第二次打印所选范围内的行,例如:

          awk '
          NR==FNR {
              if ($0 ~ /\<4320101\>/ {
                  for (i=NR-20;i<NR;i++)
                      range[i]
                  inRange = 1
              }
              if (inRange) {
                  range[NR]
              }
              if ($0 ~ /<\/eventUpdate>/) {
                  inRange = 0
              }
              next
          }
          FNR in range
          ' file file
          

          【讨论】:

          • 使用这个我得到:awk:cmd。 line:9: (FILENAME=test FNR=482) 致命:尝试使用标量 `inRange' 作为数组
          • 已修复。 awk 确实将您指向错误所在的行并告诉您错误是什么。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-09-27
          • 2015-02-01
          • 1970-01-01
          • 2013-09-09
          • 1970-01-01
          • 2020-01-06
          • 2020-08-30
          相关资源
          最近更新 更多