使用sed(或awk)删除图案上方的行范围


28

我有以下代码,该代码将删除带有模式的banana行及其后的两行:

sed '/banana/I,+2 d' file

到目前为止,一切都很好!但我需要它来除去2行之前 banana,但我不能用“减号”或任何(类似于得到它grep -v -B2 banana file应该做的,但没有):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'

1
最简单的方法是将所有数据加载到一个数组中,跳过不需要的行,然后输出剩下的内容:awk '{l[m=NR]=$0}/banana/{for(i=NR-2;i<=NR;i++)delete l[i]}END{for(i=1;i<=m;i++)if(i in l)print l[i]}'。这效率不高,所以这只是一个提示,而不是解决方案。
manatwork 2012年

6
做吧tac file | sed ... | tac。:P
安格斯

@angus我没考虑过;)
Teresa e Junior

1
您本可以做到的sed '/banana/,+2d' file 也可以
Akaks

1
如果您愿意使用awk,则非常简单: awk 'tolower($0)~/bandana/{print prev[!idx];print prev[idx]} {idx=!idx;prev[idx]=$0}' filein 由于这是评论而不是答案(已经有其他答案),因此我不会赘述过多,但关键是您始终拥有前两个记录首页上一页[0]与上一页[1]中,“新鲜”,这取决于迭代,但总是在prev[idx],所以当你打印,您在打印!idx,然后idx顺序。无论如何,请交替使用idx并将当前记录放入中prev[idx]
Luv2code

Answers:


22

Sed不会后退:处理完一行后就完成了。因此,“找到一条线并打印前N行”不会像以前那样容易工作,而不同于“找到一条线并打印后N行”。

如果文件不太长,因为您似乎对GNU扩展名没问题,因此可以使用tac来反转文件的行。

tac | sed '/banana/I,+2 d' | tac

另一个迎角是在awk之类的工具中保持滑动窗口。适应自grep的-A -B -C开关是否有其他选择(在之前和之后打印几行)?(警告:经过最少测试):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

用法: /path/to/script -v pattern='banana' -v before=2


2
sed也可以滑动窗口,但是生成的脚本通常不可读,因此使用起来更容易awk
2012年

@Gilles .. awk脚本不太正确;照原样打印空白行,错过最后几行。这似乎可以解决问题,但是它可能并不理想或本身不正确:if (NR-before in h) { print...; delete...; }...以及在本END节中:for (i in h) print h[i]...此外,awk脚本会打印出匹配的行,但tac/sec版本却不会;但是这个问题有点模棱两可。您提供了链接的“原始” awk脚本可以正常工作。.我喜欢它...我不确定上述“ mod”对打印后打印效果有何影响行...
Peter.O 2012年

@ Peter.O谢谢,awk脚本现在应该更好了。我花了不到6-8年的时间!
吉尔(Gilles)'所以

19

使用exvim -e这很容易

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

该表达式显示为:对于从当前行-2到当前行范围内的每一行包含香蕉的行,请删除。

最酷的是,该范围还可以包含向后和向前搜索,例如,这将删除文件的所有部分,这些部分以包含apple的行开始,以包含orange的行结束,并包含以香蕉的行结束:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

7

在中使用“滑动窗口” perl

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'

6

您可以使用sed

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

我不知道为什么有人会这样说,但是要找到一行并打印前几行 ,则要sed结合内置的Print原语,该原语只能\n写出图案空间中的第一个ewline字符。互补Delete原语在递归回收剩余脚本之前会删除模式空间的同一部分。为了解决这个问题,有一个原语,用于将Next输入行附加到插入的\newline字符之后的模式空间中。

这样一行sed就可以满足您的所有需求。您只要match用您的正则表达式替换掉就可以了。那也应该是一个非常快速的解决方案。

还要注意,它将正确地将match前一个计数match为触发前两个行的安静输出的触发,同时安静其打印:


1
7match
8
11match

为了使它能在任意数量的行中工作,您需要做的就是取得领先地位。

所以:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

...删除任何匹配项之前的5行。


1

使用man 1 ed

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.