如果您需要一种优雅的 perl
解决方案,请参考Sundeep的答案。
如果您需要类似且非常好的sed
解决方案,请参考potong的第二个答案
这两种解决方案都会将文件完全读入内存并一次性处理。如果您不需要处理GB级别的文件大小,那么这是可以接受的。换句话说,这些是最佳解决方案(如果我们忽略CASE3
)。
注释:这两种解决方案都无法通过CASE3
(请参见下文)。CASE3
是一个特殊的有争议的情况。
更新1:以下的awk
解决方案是一个新脚本,可在所有情况下运行。早期的解决方案在特定情况下失败(请参见下面的CASE3
)。此解决方案解决了嵌套分组(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>
- 第一行
#1
通过在保留空间(x;s...;x
)中放置单词print
并向模式空间附加另一行来初始化状态(N
)。
- 第二行
#2
将第三行添加到模式空间中。
- 第三行
#3
通过检查保留空间并删除保留空间P
,打印模式空间中的第一个\n
并用z
清除模式空间来确定是否需要打印模式空间的第一行。
- 第四行
#4
通过检查实际模式是否匹配来确定我们是否应该在下一个周期打印。如果不匹配,则将单词print
放入保留空间。
- 第五行
#5
是文件结尾条件。
- 第六行
#6
删除模式空间中的第一个\n
并返回到#1
而不读取新行。
在退出时,再次打印模式空间。
注释:如果您想查看模式空间和保留空间的外观,可以在每行后面添加以下代码:s/^/P:/;l;s/^P://;x;s/^/H:/;l;s/^H://;x
。此行将使用P:
和H:
分别在两个空间中打印。
使用的测试文件:
<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>
这个解决方案默认打印所有行,除非该行包含在一个包含bar
的行之后的foo
。上面的逻辑只是决定我们是否应该打印foo
行。
!/baz/&&(c==2){print foo}
:这解决了提前终止的问题。如果在有效的bar-foo
组合之后没有找到baz
,则打印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
的字符串,因此
RS
表示有效的
bar-foo-baz
组合。找到的记录分隔符
RT
用
sub
进行编辑以删除
foo
行,并在找到的记录之后打印。
RT
(gawk扩展)匹配由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;...}
如果模式缓冲区在新行字符后包含foo
,则将下一行追加到模式缓冲区中 (N
)
/foo.*\n.*baz[^\n]*$/{s/\n.*foo.*\n/\n/}
如果模式缓冲区包含 foo
后跟单个换行符并以包含baz
的行结尾,则删除包含foo
的行。此处搜索模式排除了类似于barfoo\nfoobaz\ncar
的情况。