【问题标题】:Deleting N matching patterns using sed, awk, perl etc使用 sed、awk、perl 等删除 N 个匹配模式
【发布时间】:2019-10-14 16:30:49
【问题描述】:

我有这种情况,文件包含

abcd
line1
line2
line3
vwxyz
abcd
vwxyz
abcd
vwxyz
abcd
vwxyz
line4
line5

只要找到匹配的 2 行,我想删除

abcd
vwxyz

被删除,使其变为

abcd
line1
line2
line3
vwxyz
line4
line5

我已经搜索了许多关于模式匹配的 stackoverflow 建议,但从未遇到过 2 行的模式匹配

我已经尝试过sed -i '/abcd|vwxyz/d' file1,但这并没有按预期工作......

感谢使用 awk、sed、perl、pyhon 等提供的任何帮助

【问题讨论】:

  • 我们认为你尝试了一些东西...代码?
  • sed -i '/abcd|vwxyz/d' file1 但没有按预期工作

标签: python perl awk sed


【解决方案1】:

你的意思是这样的?

sed '/abcd/{N;/vwxyz/d}' yourdata.file

解释

/abcd/    # search first Pattern
{
N;        # if match read next line
/vwxyz/d  # if second pattern match then delete line
}

【讨论】:

  • 如果一对行前面有abcd,这可能无法删除。
【解决方案2】:

使用 Perl,最简单的解决方案是将整个输入加载到内存中。

perl -0777pe's/abcd\nvwxyz\n//g'

如果一次读取一行,这类问题的通用解决方案是保留过去行的缓冲区。

if (!eof()) {
   my @buf = scalar(<>);
   while (<>) {
      if ($buf[-1] =~ /^abcd$/ && /^vwxyz$/) {
         @buf = ();
      } else {
         print(shift(@buf));
         push(@buf, $_);
      }
   }

   print @buf;
}

【讨论】:

    【解决方案3】:

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

    sed 'N;/^abcd\nvwxyz$/d;P;D' file
    

    在整个文件长度内创建一个 2 行窗口,如果当前窗口与所需的字符串匹配,则删除该窗口。否则打印/删除窗口的第一行并附加另一行,重复。

    这可以扩展为匹配 n 行:

    sed ':a;N;s/[^\n]\+/&/2;Ta;/^abcd\nvwxyz$/d;P;D' file
    

    概括为 2 行:

    或者:

    sed ':a;N;s/[^\n]\+/&/3;Ta;/^line1\nline2\nline3$/d;P;D' file
    

    3 行等

    另一种选择:

    sed -z 's/^abcd\nvwxyz\n//mg' file
    

    【讨论】:

    • 谢谢,第一个选项似乎很简单...我不考虑 \n :)
    • ^ 表示“字符串开头”而不是“行开头”,所以sed -z 's/^...' 不会仅匹配文件开头的文本吗?我不知道 g 之前的 m 做了什么,所以也许它是为了“magic”... :-)。
    • @EdMorton 替换命令后的m 标志用于多行,其中^$ 表示行开始/结束的锚。
    • @potong - 啊,好的,谢谢你的解释。当我刚刚尝试使用 GNU sed 4.4-1 时,该命令行只是输出输入文件不变。
    • @EdMorton 感谢 Ed 我目前使用的是 GNU sed 4.2.2 并且没有遇到这个问题。请使用第一个解决方案,如果此错误仍然存​​在,请向 GNU sed 维护者发出错误报告。我相信当前版本是 GNU sed 4.7。
    【解决方案4】:

    考虑到您的实际 Input_file 与显示的示例相同,请您尝试以下操作。

    awk -v RS="" '{gsub(/abcd\nvwxyz/,"");gsub(/[[:space:]]+\n/,ORS)} 1' Input_file
    

    【讨论】:

      【解决方案5】:

      比我想要的更神秘一点,但使用 GNU awk 用于多字符 RS:

      $ awk -v RS='\nvwxyz\n' -v ORS= '!sub(/(^|\n)abcd$/,""){$0=$0 RT} 1' file
      abcd
      line1
      line2
      line3
      vwxyz
      line4
      line5
      

      不幸的是,如果我们使用基于 RS 的解决方案(而不是保持滚动的 2 行缓冲区或类似的),那么必须这样做以适应出现在开头的多行字符串和/或文件结尾和/或在重复的块中和/或在其他行的中间开始/停止。不过,它可以按原样用于任意长的行块:

      awk -v RS='\nvwxyz\n' -v ORS= '!sub(/(^|\n)foo\nbar\netc\nabcd$/,""){$0=$0 RT} 1' file
      

      或者如果您愿意:

      awk -v RS='\nbar\netc\nabcd\nvwxyz\n' -v ORS= '!sub(/(^|\n)foo$/,""){$0=$0 RT} 1' file
      

      【讨论】:

        【解决方案6】:

        使用awk,您可以将记录分隔符定义为两行,然后只需打印每一行。

        awk -v RS='abcd\nvwxyz\n' '{printf $0}' file.in 
        abcd
        line1
        line2
        line3
        vwxyz
        line4
        line5
        

        一个python解决方案:

        import re
        
        with open('file.in', 'r') as file:
            data = file.read()
            print(re.sub(r'(^|\n)abcd\nvwxyz(?=\n)','',data), end='')
        

        输出:

        python3 filter_lines.py 
        abcd
        line1
        line2
        line3
        vwxyz
        line4
        line5
        

        【讨论】:

        • 您应该提到,多字符 RS 需要 GNU awk。始终使用printf "%s", $0 而不是printf $0,因为当$0 包含诸如%s 之类的printf 格式字符时,后者将失败。它实际上应该写为awk -v RS='(^|\n)abcd\nvwxyz\n' '1',但可以处理abcd 和/或vwxyz 可能出现在文件中线和/或文件开头和/或结尾的情况。我觉得那仍然缺少一个案例....
        • 啊,是的 - 我的评论中那个 RS 的问题是,当有 2 个连续块时,它与第二个 \nabcd\nvwxy\n 不匹配,因为它在每个块的两端都消耗了 \n。我必须考虑一下真正需要的 RS - 它可能根本不是一个选择。
        • 经过一番思考 - 你不能仅仅通过将 RS 设置为目标多行字符串以及周围的行开始/结束来稳健地做到这一点,所以我发布了一个新的答案。
        猜你喜欢
        • 1970-01-01
        • 2020-01-24
        • 2015-05-07
        • 2020-04-15
        • 2014-09-07
        • 1970-01-01
        • 2020-04-25
        • 2011-05-25
        • 1970-01-01
        相关资源
        最近更新 更多