【问题标题】:get specific lines from a repeated range pattern in a text file从文本文件中的重复范围模式中获取特定行
【发布时间】:2013-06-26 07:15:06
【问题描述】:

哇,这个标题听上去好复杂,但我想并不完全如此。

我的文本文件基本上具有这种布局:

Stimulus ...
...
...
...
Response
Stimulus ...
...
...
...
Response

我使用 sed 获取介于两者之间的所有内容,然后进一步提取我需要的信息。

sed -n -e '/Stimulus/,/Response/ p'

但是,有时参与者没有响应,在这种情况下,文件如下所示:

Stimulus ...
...
...
...
Stimulus ...
...
...
...
Response

在这种特殊情况下,我的脚本将无法获得我要查找的内容。因此,我正在寻找一种方法来提取信息,当且仅当 pattern1 后跟 pattern2,而不是 pattern1。

如果我表述不清楚,请告诉我。我很乐意提供更多信息。

【问题讨论】:

  • '/Stimulus/,/Response|Stimulus/ 有效吗?嗯。。应该不会吧。这通常通过一个 awk 脚本来解决,该脚本为两个值设置一个标志,当两者都为真时,做你需要做的事情。祝你好运。

标签: regex perl sed awk pattern-matching


【解决方案1】:

一个肮脏的方法,虽然它似乎在我的测试中工作,可能是反转文件内容,从 Response 搜索到 Stimulus 并再次反转结果。

假设以下输入数据:

Stimulus 1...
...
...
...
Stimulus 2...
...
...
...
Response 2
Stimulus 3...
...
...
...
Response 3
Stimulus 4...
...
...
...
Stimulus 5...

命令:

tac infile | sed -ne '/Response/,/Stimulus/ p' | tac -

产量:

Stimulus 2...
...
...
...
Response 2
Stimulus 3...
...
...
...
Response 3

编辑:例如,带有独立的 Response 部分。有两次过滤(基于OP的评论):

tac infile | 
  sed -ne '/Response/,/Stimulus/ p' | 
  tac - | 
  sed -ne '/Stimulus/,/Response/ p'

【讨论】:

  • 优雅。将对其进行测试并回来。
  • 我测试了它,有一个小问题,因为不仅有孤立的刺激部分,还有孤立的响应部分。但是,执行该过程两次就可以了:tac infile | sed -ne '/响应/,/刺激/ p' | tac - | sed -ne '/Stimulus/,/Response/ p' ;)
  • 从而使它成为井字游戏!
【解决方案2】:

这是一个纯粹的 解决方案:

tmp=()
while read l; do
  [[ $l =~ ^Stimulus ]] && tmp=("$l") && continue
  [ ${#tmp[@]} -eq 0 ] && continue
  tmp+=("$l")
  [[ $l =~ ^Response ]] && printf "%s\n" "${tmp[@]}" && tmp=()
done <infile

如果找到以Stimulus 开头的列表,它将开始填充数组tmp。如果另一个Stimulus 到达,它只会清除tmp 并重新开始工作。如果找到Response,它将打印tmp 数组的内容。实际上 printf 内置做了一个隐式循环。

输入:

cat >infile <<XXX
...
Response 0
...
Stimulus 1
...
Stimulus 2
...
Response 2
...
Stimulus 3
...
Response 3
...
Response 4
XXX

输出:

Stimulus 2
...
Response 2
Stimulus 3
...
Response 3

【讨论】:

  • 将在每个Response 上输出内容,即使之前没有Stimulus。此外,正则表达式是多余的:[[ $l = Response* ]] 就足够了。
  • @gniourf_gniourf:如果没有以Stimulus 开头的行,你能告诉我输出是什么吗?我猜是空行。 :) 当然,您可以使用 glob 匹配。我更喜欢regex
  • 您在每个Response 上输出tmp 数组,无论发生什么,即使之前没有任何Stimulus。在这方面,您并没有完全回答 OP。请在@Birei 的回答中查看OP 的评论。
  • @gniourf_gniourf:你是对的。这是原始问题的解决方案。我更正了,谢谢!
  • 我们现在几乎拥有相同的算法。 +1:).
【解决方案3】:

其他选项是切换到perl 及其触发器(范围运算符):

perl -lne '
    BEGIN {
        ## Create regular expression to match the initial and final words.
        ($from_re, $to_re) = map { qr/\A$_/ } qw|Stimulus Response|;
    }
    ## Range, similar to "sed".
    if ( $r = ( m/$from_re/o ... m/$to_re/o ) ) {
        ## If inside the range and found the initial word again, remove 
        ## all lines saved.
        if ( $r > 1 && m/$from_re/o ) {
            @data = ();
        }
        ## Save line.
        push @data, $_;
        ## At the end of the range, print all lines saved.
        if ( $r =~ m/E0\z/ ) {
            printf qq|%s\n|, join qq|\n|, @data;
            @data = ();
        }
    }
' infile

假设输入文件为:

Stimulus 1...
...
...
...
Stimulus 2...
...
...
...
Response 2
Stimulus 3...
...
...
...
Response 3
Stimulus 4...
...
...
...
Stimulus 5...

它产生:

Stimulus 2...
...
...
...
Response 2
Stimulus 3...
...
...
...
Response 3

【讨论】:

    【解决方案4】:

    这是一个纯粹的 解决方案,它试图将愚蠢的副作用降到最低:

    #!/bin/bash
    
    out=()
    
    while read -r l; do
       case "$l" in
           Stimulus*) out=( "$l" ) ;;
           Response*) ((${#out[@]}!=0)) && { printf "%s\n" "${out[@]}" "$l"; out=(); } ;;
           *) ((${#out[@]}!=0)) && out+=( "$l" ) ;;
       esac
    done < infile
    

    它还处理有Response 但没有Stimulus 的情况。

    【讨论】:

      【解决方案5】:

      已更新以处理孤立的响应

      awk '
      /Response/ { 
          if (p==1) {
              for(;k<length(a);) {
                  print a[++k]
              }
              print $0
          }
          delete a;k=p=0
      } 
      /Stimulus/ {
          if (p==1) {
              delete a; i=0
          }
          p=1
      } 
      p { a[++i]=$0 }' log
      

      【讨论】:

        【解决方案6】:

        对于 GNU 来说,这是一项非常好的和简单的工作,单向,没有不需要的管道和工具:

        sed -n 'H;/^Stimulus/{h;d};/^Response/{x;s/^Response//;tk;p;:k;d}' file
        

        输入文件:

        刺激1... 坏的 坏的 坏的 刺激2... ... ... ... 回应 2 刺激 3... ... ... ... 回应 3 刺激 4... 坏的 坏的 坏的 坏的 刺激 5... ... ... ... ... 回应 5 坏的 坏的 坏的 坏的 回应 6 坏的 坏的 坏的

        然后输出:

        $sed -n 'H;/^Stimulus/{h;d};/^Response/{x;s/^Response//;tk;p;:k;d}' 文件 刺激2... ... ... ... 回应 2 刺激 3... ... ... ... 回应 3 刺激 5... ... ... ... ... 回应 5

        还有我的 GNU 代码

        awk '{a[++i]=$0};/^Response/ && a[1] !~ /^Response/ {for (k=1; k<=i; k++) {print a[k]}};/^Stimulus|^Response/ { delete a; i=0; a[++i]=$0}' file
        

        如你所见,我需要太多的 awk 代码...

        【讨论】:

          猜你喜欢
          • 2013-06-25
          • 2013-06-20
          • 2021-11-23
          • 1970-01-01
          • 1970-01-01
          • 2018-11-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多