使用sed和/或awk,我希望只有当它包含字符串"foo"时才能删除一行,并且前后的行分别包含字符串"bar"和"baz".
所以对于这个输入:
blah
blah
foo
blah
bar
foo
baz
blah
Run Code Online (Sandbox Code Playgroud)
我们会删除第二个foo,但没有别的,留下:
blah
blah
foo
blah
bar
baz
blah
Run Code Online (Sandbox Code Playgroud)
我尝试使用while循环逐行读取文件,但这很慢,我无法弄清楚如何匹配上一行和下一行.
编辑 - 根据评论中的要求,这是我的while循环的当前状态.目前只匹配前一行(从前一个循环存储为$ linepre).
linepre=0
while read line
do
if [ $line != foo ] && [ $linepre != bar ]
then
echo $line
fi
linepre=$line
done < foobarbaz.txt
Run Code Online (Sandbox Code Playgroud)
蛮丑的.
有关优雅的perl
解决方案,请参阅Sundeep的答案.
对于类似且非常好的sed
解决方案,请参阅potong的第二个答案
两种解决方案都将文件完全读入内存并一次处理.如果您不需要处理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>
Run Code Online (Sandbox Code Playgroud)
为了解决这个问题,我们不断缓冲存储在中的3行l1
,l2
和$0
.每次处理新行时,我们确定是否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
Run Code Online (Sandbox Code Playgroud)
#1
通过将单词print
放在hold空间(x;s...;x
)中来初始化状态,并将另一行附加到模式空间(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:
在前面打印两个空格.
二手测试文件:
# 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
Run Code Online (Sandbox Code Playgroud)
以前接受的答案:(更新以指示哪些案例失败)
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>
Run Code Online (Sandbox Code Playgroud)
此解决方案默认打印所有行,除非行包含在包含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>
Run Code Online (Sandbox Code Playgroud)
将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>
Run Code Online (Sandbox Code Playgroud)
/bar/{N;...}
如果该行包含bar
,则将下一行追加到模式缓冲区(N
)/\n.*foo/{N;...}
如果图案缓冲器具有foo
一个换行字符之后,追加下一行的图案缓冲器(N
)/foo.*\n.*baz[^\n]*$/{s/\n.*foo.*\n/\n/}
如果模式缓冲区包含foo
后跟单个换行符并以包含的行结束baz
,则删除包含的行foo
.这里的搜索模式将案例排除在外barfoo\nfoobaz\ncar
归档时间: |
|
查看次数: |
490 次 |
最近记录: |