使用sed / awk,我需要从第一次出现的pattern1中删除文件中的所有行,直至(但不包括)最后一次出现的pattern2

时间:2016-02-02 21:38:26

标签: regex bash awk sed

使用sed / awk,我需要删除文件中的所有行,从第一次出现的pattern1到(但不包括)最后一次出现的pattern2。

考虑以下文字:

    <entity name="good">
    </entity>
    <entity name="bad">
    stuff to delete
    </entity>
    <entity name="bad">
    stuff to remove
    </entity>
    <entity name="bad2">
    </entity>
    <entity name="deleteMe2">
    </entity>
    <entity name="bad2">
    </entity>
    <entity name="good">
    </entity>

我想得到以下结果

<entity name="good">
</entity>
<entity name="bad2">
</entity>
<entity name="good">
</entity>

我知道如何在sed中执行范围,但无法弄清楚如何匹配最后一次出现的'bad2'而不包括在删除中。下面当然不会起作用,因为它会匹配第一个bad2而不会删除'badme2'或'bad2'的第二次发生。

sed -i '/<entity name="bad"/,/<entity name="bad2"/d' file.xml

我正在处理的文件中可能有数百个“坏”/“deleteMe2”/“bad2”行,因此简单的行数不起作用。我很好,如果这是多个命令(它不必只是一个),但效率越高越好,因为被修改的文件可能非常大。同样,-i是因为我想要删除之间的行。

注意:我对SED比对AWK更熟悉,但我愿意接受所有帮助:)

4 个答案:

答案 0 :(得分:1)

这看起来像XML,所以我强烈建议regex不是工作的工具。改为使用解析器:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig -> new -> parsefile ( 'your_file.xml' ) ;
$_ -> delete for $twig -> findnodes ( '//entity[@name="bad"]');
$twig -> set_pretty_print('indented_a');
$twig -> print;

或者可能更全面:

for my $entity ( $twig -> findnodes ( '//entity') ) {
   if ( $entity -> att('name') eq "bad"
   or   $entity -> att('name') eq "deleteMe2" ) {
           $entity -> delete; 
   }
}

要仅删除'bad2'的第一个实例,您只需拨打findnodes一次,然后删除第一个'匹配'。

答案 1 :(得分:1)

$ cat tst.awk
NR==FNR {
    if (/"bad"/ && !begFnr) {
        begFnr = FNR
    }
    if (/"bad2"/) {
        endFnr = FNR
    }
    next
}
(FNR < begFnr) || (FNR >= endFnr)

$ awk -f tst.awk file file
<entity name="good">
</entity>
<entity name="bad2">
</entity>
<entity name="good">
</entity>

答案 2 :(得分:0)

awk救援!

$ awk 'NR==FNR&&/\"bad\"/&&!s{s=NR;next} 
          NR==FNR&&/\"bad2\"/{e=NR;next} 
          NR!=FNR && (FNR<s || FNR>=e)' xml{,}

    <entity name="good">
    </entity>
    <entity name="bad2">
    </entity>
    <entity name="good">
    </entity>

我猜可以进一步简化。两个通过脚本首先标记行号并第二次打印。

答案 3 :(得分:0)

这可能适合你(GNU sed):

 sed '/bad/,$!b;/bad2/h;//!H;$!d;g;/bad2/!d' file

不在bad和文件末尾之间的行,正常打印。否则,在匹配bad2时,将这些行存储在保留空间中,覆盖这些存储的行。删除除最后一行之外的所有行,将其替换为保留空间的内容。删除该行,除非它与bad2匹配。