【问题标题】:How to delete all the lines after the last occurence of pattern?如何删除最后一次出现模式后的所有行?
【发布时间】:2017-06-03 05:55:29
【问题描述】:

我想删除最后一次出现模式之后的所有行,除了模式本身

文件.txt

honor
apple
redmi
nokia
apple
samsung
lg
htc

file.txt 我想要什么

honor
apple
redmi
nokia
apple

我尝试过的

sed -i '/apple/q' file.txt

这将删除第一次出现模式后的所有行 -

honor

【问题讨论】:

  • 反转文件,删除模式之前的所有内容,然后反转结果。
  • 文件其实很大。会有什么问题吗?还有其他有效的方法吗?
  • 找不到字符串怎么办——打印全部还是什么都不打印?

标签: bash awk sed


【解决方案1】:

使用几乎不使用内存的简单、稳健的 2 遍方法:

$ awk 'NR==FNR{if (/apple/) hit=NR; next} {print} FNR==hit{exit}' file file
honor
apple
redmi
nokia
apple

如果执行速度不够快那么,是时候尝试一些替代方案,看看是否能提高性能。

【讨论】:

  • 为什么filename参数需要传入两次?你能解释一下在这种情况下 awk 在幕后所做的事情的机制吗?
【解决方案2】:

反转文件,从模式的第一次出现开始打印所有内容,然后反转结果:

tac file.txt | sed -n '/apple/,$p' | tac > newfile.txt

你可以找到最后一个匹配的行号,然后用它来打印文件的前 N ​​行:

line=$(awk '/apple/ { line=NR } END {print line}')
head -n $line file.txt > newfile.txt

【讨论】:

  • 如果文件中不存在apple,那么这些脚本将不会产生任何输出,但我认为预期的行为是打印整个文件。顺便说一句,您需要在 awk 脚本的末尾添加 file.txt
【解决方案3】:

如果您不想像 Barmar 建议的那样反转文件,则必须使用较低级别的工具(例如 fseek)反向读取文件或读取两次:

sed $(awk '/apple/{a=NR}END{print a+1}' input),\$d input

(请注意,如果模式没有出现在文件中,则不会输出任何内容。这是您应该担心的边缘情况。)

【讨论】:

  • 在边缘盒上很好看!当然,您可以使用print (a?a:NR)+1 来解决这个问题,尽管我个人更喜欢识别要打印的行而不是要删除的行(对我来说感觉更像是“积极”的逻辑),例如:sed -n $(awk '/apple/{a=NR}END{print (a?a:NR)}' input),\$p input,当然还有一些封闭的引号是很好,你可以使用head 而不是sed...
【解决方案4】:

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

sed '/apple/,$!b;//!H;//{x;//p;x;h};${x;P};d' file

像往常一样打印不是从apple 的第一次出现到文件末尾的任何行。对于上述范围内的行,将不包含单词 apple 的行附加到保留空间 (HS)。确实包含单词 apple 的行,首先交换到 HS 并打印那里如果单词 apple 存在的任何行,然后将 HS 替换为包含 apple 的行。删除除最后一行之外的所有行。在文件末尾打印 HS 的第一行并删除剩余的行。

如果啜饮大文件不成问题,请使用:

sed -rz 's/(.*apple[^\n]*).*/\1\n/' file

这使用贪婪来捕获单词apple之前和包括单词的所有行。

【讨论】:

    【解决方案5】:

    这里是另一个awk,没有扫描两次文件

    $ awk 'f       {buf=buf ORS $0} 
           /apple/ {f=1; if(buf)print buf; buf=$0} 
           !f' file
    
    honor
    apple
    redmi
    nokia
    apple
    

    【讨论】:

    • 如果apple 出现在输入中最后一个apple 行之前的行上,则会产生意外的输出(它将被打印两次)。 OP 在评论中还提到,他担心他的文件太大,所以 YMMV 将它的块存储在内存中,因为它今天可能工作,然后在其他一些“苹果”分发中失败。
    【解决方案6】:

    如果您不介意将所有内容都保存在内存中,您可以这样做:

    $ awk '/^apple$/{last=NR} 
                  {lines[NR]=$0}
         END{for(li=1;li<=last;li++) print lines[li]}' file
    honor
    apple
    redmi
    nokia
    apple
    

    【讨论】:

      【解决方案7】:

      鉴于您正在处理大量输入,我将采用两遍 coreutils 解决方案,例如:

      n=$(grep -Fn apple infile | tail -n1 | cut -d: -f1)
      [ -n "$n" ] && head -n$n infile > outfile
      

      这使用 grep 的 固定字符串匹配 (-F) 来查找包含苹果的每一行。然后用head提取相关行。

      您没有指定找不到苹果时会发生什么,因此当这种情况发生时,此解决方案不会执行任何操作。

      【讨论】:

      • 你错过了一个结束')'(我不能自己编辑你的答案)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      • 1970-01-01
      • 2017-01-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多