使用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更熟悉,但我愿意接受所有帮助:)
答案 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
匹配。