使用sed和/或awk,我希望能够删除一行,只要它包含字符串“foo”并且前后的行分别包含字符串“bar”和“baz”。
所以对于这个输入:
blah
blah
foo
blah
bar
foo
baz
blah
我们会删除第二个foo,但没有别的,留下:
blah
blah
foo
blah
bar
baz
blah
我尝试使用while循环逐行读取文件,但这很慢,我无法弄清楚如何匹配上一行和下一行。
编辑 - 根据评论中的要求,这是我的while循环的当前状态。目前只匹配前一行(从前一个循环存储为$ linepre)。
linepre=0
while read line
do
if [ $line != foo ] && [ $linepre != bar ]
then
echo $line
fi
linepre=$line
done < foobarbaz.txt
非常难看。
答案 0 :(得分:5)
要获得优雅的 perl
解决方案,请参阅Sundeep's answer。
对于类似且非常好的 sed
解决方案,请参阅potong's second answer
两种解决方案都将文件完全读入内存并一次处理。如果您不需要处理GB文件大小,这很好。换句话说,这些是最佳解决方案(如果我们忽略CASE3
)。
评论: 两个解决方案都失败CASE3
(见下文)。 CASE3
是一个特别值得商榷的案例。
更新1:以下awk
解决方案是一个适用于所有情况的新脚本。在特定情况下,此答案被接受的早期解决方案失败了。所提出的解决方案解决了嵌套分组(下面的CASE3
):
awk 'BEGIN{p=1;l1=l2=""}
(NR>2) && p {print l1}
{ p=!(l1~/bar/&&l2~/foo/&&/baz/);
l1=l2;l2=$0
}
END{if (l1!="" && p) print l1
if (l2!="" ) print l2}' <file>
要解决此问题,我们会不断缓存存储在l1
,l2
和$0
中的3条线。每次处理新行时,我们确定是否应在下一个周期中打印l1
并交换缓冲行。打印仅从NR=3
开始。要打印的条件是,如果l1
包含bar
,l2
包含foo
且$0
包含baz
,那么我们不会在下一个打印周期。
更新2:可以获得基于相同原则的sed
解决方案。 sed
有两个回忆。 模式空间是您执行所有操作的地方,保留空间是长期记忆。我们的想法是将单词print
放在保留空间中,但我们只能通过交换周围的空格(使用x
)来实现这一点
sed '1{x;s/^.*$/print/;x;N}; #1
N; #2
x;/print/{z;x;P;x};x; #3
/bar.*\n.*foo.*\n.*baz/!{x;s/^.*$/print/;x}; #4
$s/\(bar.*\)\n.*foo.*\n\(.*baz\)/\1\n\2/; #5
D' <file> #6
#1
通过将单词print
放入保留空间(x;s...;x
)并将另一行附加到模式空间(N
)#2
将第三行添加到模式空间#3
通过检查保留空间确定是否需要打印模式空间的第一行,并删除保留空间P
打印到模式空间中的第一个\n
和z
zaps模式空间#4
确定我们是否应该在下一个周期中打印。检查实际模式是否匹配,如果没有将单词print
放入保留空间#5
,是文件结束条件#6
删除模式空间中的第一个\n
,然后返回#1
而不读取新行。退出时,将再次打印图案空间。
评论:如果您想查看模式空间和保留空间的外观,可以在每行后添加以下代码:s/^/P:/;l;s/^P://;x;s/^/H:/;l;s/^H://;x
。此行将在前面分别打印P:
H:
个空格。
使用过的测试文件:
# bar-foo-baz test file
# An asterisk indicates the foo
# lines that should be removed
<CASE0 :: default case>
bar
foo (*)
baz
<CASE1 :: reset cycle on second line>
bar
foobar
foo (*)
baz
<CASE2 :: start cycle at end of previous cycle>
bar
foo (*)
bazbar
foo (*)
baz
<CASE3 :: nested cases>
bar
foobar (*)
foobaz (*)
baz
<CASE4 :: end-of-file case>
bar
foo
以前接受的答案:(已更新,表明哪些案例失败)
awk
:失败CASE3
awk '!/baz/&&(c==2){print foo}
/bar/ {c=1;print;next}
/foo/ &&(c==1){c++;foo=$0;next}
{c=0;print}
END{if(c==2){print foo}}' <file>
此解决方案默认打印所有行,除非该行包含foo
,该行位于包含bar
的行之后。上面的逻辑决定我们是否应该打印行foo
。
!/baz/&&(c==2){print foo}
:这解决了提前终止问题。如果在有效的baz
组合后找不到bar-foo
,则会打印foo
行。
/bar/{c=1;print;next}
:这会初始化新周期的开始。如果找到bar
,请将c
设置为1
,打印该行并移至下一行。始终会打印bar
行。此行可解析CASE1
和CASE2
。
/foo/&&(c==1){c++;foo=$0;next}
:这会检查bar-foo
组合。它存储foo
行并移至下一行。
{c=0;print}
,如果我们达到这一点,就意味着我们找不到bar
行或bar-foo
组合。只需在默认情况下打印该行,并将计数器重置为零。
END{if(c==2){print foo}}
此声明仅解决CASE4
gawk
:失败CASE3
awk 'BEGIN{ORS="";RS="bar[^\n]*\n[^\n]*foo[^\n]*\n[^\n]*baz"}
{sub(/\n[^\n]*foo[^\n]*\n/,"\n",RT); print $0 RT}' <file>
RS
设置为bar[^\n]*\n[^\n]*foo[^\n]*\n[^\n]*baz
,即我们感兴趣的模式。此处,[^\n]*\n[^\n]*
表示包含单个\n
的字符串,因此{{1} }}表示有效的RS
组合。使用bar-foo-baz
编辑找到的记录分隔符RT
以删除sub
行,并在找到的记录后打印。
foo
(gawk扩展名)与表示的文字相匹配的输入文字RT
,记录分隔符。每次读取记录时都会设置它。
RS
:失败sed
CASE1, CASE2, CASE3, CASE4
sed -n '/bar/{N;/\n.*foo/{N;/foo.*\n.*baz[^\n]*$/{s/\n.*foo.*\n/\n/}}};p' <file>
如果该行包含/bar/{N;...}
,则将下一行追加到模式缓冲区(bar
)N
如果模式缓冲区在换行符后面有/\n.*foo/{N;...}
,请将下一行追加到模式缓冲区({{1} })foo
如果模式缓冲区包含N
后跟一个换行符,并以包含/foo.*\n.*baz[^\n]*$/{s/\n.*foo.*\n/\n/}
的行结束,请删除包含foo
的行}。此处的搜索模式将案例排除为baz
答案 1 :(得分:3)
针对更多异国情调的修改样本:
$ cat ip.txt
blah
bar
blah
foo
blah
bar
foo
baz
blah
bar
foobar
foo
baz
asdf
如果perl
没问题且输入文件小到足以满足内存要求
$ perl -0777 -pe 's/bar.*\n\K.*foo.*\n(?=.*baz)//g' ip.txt
blah
bar
blah
foo
blah
bar
baz
blah
bar
foobar
baz
asdf
-0777
扼杀整个输入文件bar.*\n\K
检查上一行是否包含bar
.*foo.*\n
当前行包含foo
(?=.*baz)
下一行包含baz
答案 2 :(得分:3)
这可能适合你(GNU sed):
sed ':a;/bar/!b;n;/foo/!ba;N;s/^.*\n\(.*baz\)/\1/;t;P;D' file
如果当前行不包含bar
,则打印它并开始新的循环。否则,打印包含bar
的行,并将下一行读入模式空间。如果该行不包含foo
,请返回并检查它是否包含bar
。否则,将下一行追加到当前行(包含foo
)并检查附加行是否包含baz
。如果确实删除了包含foo
的第一行,然后打印包含baz
的行并开始新的循环。否则,附加行不包含baz
,因此请打印包含foo
的行并将其删除,然后检查附加行是否包含bar
。
另一种方法是将整个文件放入内存:
sed -zr 's/(bar[^\n]*)\n[^\n]*foo[^\n]*(\n[^\n]*baz)/\1\2/g' file
答案 3 :(得分:2)
解决方案1: 对于同一个相同的文件(您显示的),没有任何其他条件可能会对您有所帮助。
awk '/^bar/ && getline var ~ /^foo/ && getline var1 ~ /^baz/{print "bar" ORS "baz";next} 1' Input_file
解决方案第二: 关注awk
可能对您有帮助。
awk '/bar/{val=FNR} /^foo/ && ++val==FNR{value=$0;getline;if($0 ~ /^baz/){print value ORS $0;val="";next} else {print value}} 1' Input_file
上述第二个代码的不同排列和组合检查:
情境1: 当字符串bar
字符串foo
和字符串baz
出现时,它会正常工作。
情况第二: 当字符串bar
出现,然后字符串baz
没有foo
时,它也应该有效。< / p>
答案 4 :(得分:0)
sed -r ':l; N; $!bl; s/(^|\nbar\n)foo\n(baz$|\n)/\1\2/g' input.txt
或相同,但更短,可能更快,使用-z
选项:
sed -zr 's/(^|\nbar\n)foo\n(baz\n|$)/\1\2/g' input.txt
-z =按NUL字符分隔行。此选项可用于将所有文本放入内存(如果文本没有NUL字符)。
grep --color=always -Pz '\^|\nbar\n\Kfoo\n(?=baz\n)' input.txt | sed '/31m/d'
两种变体都会在处理之前将所有文本放入内存中,因此对于大文件而言,它们并非最佳。
<强>输入强>
blah
blah
foo
blah
bar
foo
baz
blah
<强>输出强>
blah
blah
foo
blah
bar
baz
blah