【问题标题】:How to get lines from the last match to the end of file?如何获取从最后一个匹配到文件末尾的行?
【发布时间】:2024-01-17 16:50:01
【问题描述】:

需要在最后一个匹配后打印行到文件末尾。匹配的数量可以是任意的,并且不是确定的。我有一些如下所示的文字。

MARKER
aaa
bbb
ccc
MARKER
ddd
eee
fff
MARKER
ggg
hhh
iii
MARKER
jjj
kkk
lll

想要的输出是

jjj
kkk
lll

我是否将 awk 与 RS 和 FS 一起使用以获得所需的输出?

【问题讨论】:

  • 我在awk 解决方案之上添加了grepsed 解决方案。
  • 所需的输出是否也包含MARKER 或只是您显示的内容?
  • @Allan 没有问题。谢谢。我在 perl 脚本中使用您答案中的第一个命令。 Perl 脚本将 $0 解释为脚本名称,而不是 EOF 之前的行。有没有办法解决这个问题?
  • @Allan 这是我的 perl 脚本中的行 "$data = `zcat file.gz | awk -v RS='MARKER\n' 'END{printf $0}' | grep 'Data : '`"
  • @Allan 没关系。我必须逃脱$ 0。 printf \$0

标签: shell awk sed grep


【解决方案1】:

请您尝试关注一下。

tac file | awk '/MARKER/{print val;exit} {val=(val?val ORS:"")$0}' | tac

这种方法的好处是awk 将只读取 Input_file 的最后一个块(这实际上是awktac 反向打印之后的第一个块)并在此之后退出。

解释:

tac file |                      ##Printing Input_file in reverse order.
awk '
  /MARKER/{                     ##Searching for a string MARKER in a line of Input_file.
    print val                   ##Printing variable val here. Because we need last occurrence of string MARKER,which has become first instance after reversing the Input_file.
    exit                        ##Using exit to exit from awk program itself.
  }
  {
    val=(val?val ORS:"")$0      ##Creating variable named val whose value will be keep appending to its own value with a new line to get values before string MARKER as per OP question.
  }
' |                             ##Sending output of awk command to tac again to make it in its actual form, since tac prints it in reverse order. 
tac                             ##Using tac to make it in correct order(lines were reversed because of previous tac).

【讨论】:

  • 谢谢,成功了!!你能解释一下awk代码吗?
  • @Arteezy,很高兴它对你有所帮助,现在为我的代码添加了完整的解释,干杯。
  • @Arteezy:我也添加了一个 grep 解决方案!
【解决方案2】:

您实际上可以使用 awk (gawk) 来完成此操作,而无需使用任何管道。

$ awk -v RS='(^|\n)MARKER\n' 'END{printf "%s", $0}' file
jjj
kkk
lll

说明:

  • 您通过RS='(^|\n)MARKER\n' 将记录分隔符定义为(^|\n)MARKER\n,默认为EOL 字符
  • 'END{printf "%s", $0}' => 在文件末尾,打印整行,因为 RS 设置为 (^|\n)MARKER\n$0 将包括所有行,直到 EOF。


另一种选择是使用grep (GNU):
$ grep -zoP '(?<=MARKER\n)(?:(?!MARKER)[^\0])+\Z' file
jjj
kkk
lll

说明:

  • -z 使用 ASCII NUL 字符作为分隔符
  • -o 仅打印匹配项
  • -P 激活 perl 模式
  • PCRE 正则表达式:(?&lt;=MARKER\n)(?:(?!MARKER)[^\0])+\Z 在这里解释https://regex101.com/r/RpQBUV/2/


最后但同样重要的是,还可以使用以下sed 方法:
sed -n '/^MARKER$/{n;h;b};H;${x;p}' file
jjj
kkk
lll

说明:

  • n跳到下一行
  • h用当前行替换保持空间
  • H 做同样的事情,但不是替换,而是追加
  • ${x;p} 在文件交换结束时 (x) 保留空间和模式空间并打印 (p)

可以变成:

tac file |  sed -n '/^MARKER$/q;p' | tac

如果我们使用tac

【讨论】:

    【解决方案3】:

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

    sed -nz 's/.*MARKER.//p' file
    

    这使用贪婪删除所有行,包括最后一次出现的MARKER

    【讨论】:

      【解决方案4】:

      你也可以试试 Perl

      $ perl -0777 -ne ' /.*MARKER(.*)/s and print $1 ' input.txt
      
      jjj
      kkk
      lll
      
      $
      

      【讨论】:

        【解决方案5】:

        最简单的记忆:

        tac fun.log | sed "/MARKER/Q" | tac
        

        【讨论】:

          【解决方案6】:

          awk 解决方案适用于任何操作系统上的任何awk 版本:

          awk '/^MARKER$/ {s=""; next}  {s = s $0 RS} END {printf "%s", s}' file
          
          jjj
          kkk
          lll
          

          【讨论】:

            最近更新 更多