如何grep-inverse-match和排除“前”和“后”行


26

考虑一个包含以下条目的文本文件:

aaa
bbb
ccc
ddd
eee
fff
ggg
hhh
iii

给定一个模式(例如fff),我想grep上面的文件以获取输出:

all_lines except (pattern_matching_lines  U (B lines_before) U (A lines_after))

例如,如果B = 2A = 1,则模式=的输出fff应为:

aaa
bbb
ccc
hhh
iii

如何使用grep或其他命令行工具执行此操作?


请注意,当我尝试:

grep -v 'fff'  -A1 -B2 file.txt

我没有得到我想要的。我得到:

aaa
bbb
ccc
ddd
eee
fff
--
--
fff
ggg
hhh
iii

Answers:


9

在大多数情况下,这样做可能会更好,但以防万一文件真的很大,而您又无法sed处理那么大的脚本文件(可能会在大约5000行脚本中发生),这就是平原sed

sed -ne:t -e"/\n.*$match/D" \
    -e'$!N;//D;/'"$match/{" \
            -e"s/\n/&/$A;t" \
            -e'$q;bt' -e\}  \
    -e's/\n/&/'"$B;tP"      \
    -e'$!bt' -e:P  -e'P;D'

这是所谓的输入滑动窗口的示例。在尝试打印任何内容之前,它会通过建立-count行的超前缓冲区$B来工作。

实际上,也许我应该澄清一下我的观点:该解决方案和不适用解决方案的主要性能限制因素将与间隔直接相关。此解决方案将以较大的间隔大小变慢,而do's将以较大的间隔频率变慢。换句话说,即使输入文件很大,如果实际间隔出现的次数仍然很少,那么他的解决方案可能就是解决之道。但是,如果间隔大小相对可控,并且很可能经常发生,那么这是您应该选择的解决方案。

所以这是工作流程:

  • 如果$match在模式空间中发现前一条\newline,sed则会递归地D删除\n它之前的每条ewline。
    • $match之前完全清除了模式空间-但要轻松处理重叠,留下一个界标似乎更好。
    • 我也尝试过一口气s/.*\n.*\($match\)/\1/躲过循环,但是当循环$A/$B变大时,Delete循环的速度要快得多。
  • 然后,我们在N输入的前一行加上\newline分隔符,然后再次尝试通过引用我们最近使用的正则表达式w / 再次D删除/\n.*$match/一次//
  • 如果模式空间匹配$match,则只能$match在行的开头进行此操作- $B清除所有之前的行。
    • 因此,我们开始循环$A
    • 这个循环的每次运行,我们会尝试s///ubstitute为&自己的$A\n中模式空间ewline的性格,如果成功,tEST将转移我们-我们整个$A压脚提升缓冲-出剧本完全超过从顶部开始的脚本与下一个输入行(如果有)。
    • 如果test不成功,我们将b退回到:top标签并递归输入另一行-如果$match在收集$After 时发生循环,则可能会开始循环。
  • 如果我们闯过一个$match函数循环,那么我们会尽量pRINT的$最后一行,如果这是它,如果!不尝试s///ubstitute为&自己的$B\n中模式空间ewline字符。
    • 我们也会对此进行t评估,如果成功的话,我们将转到:Print标签。
    • 如果没有,我们将跳转回:top并将另一行输入追加到缓冲区。
  • 如果我们要进行:Print,我们将进行Print,然后选出模式空间中D的第一个\newline,然后从顶部重新运行剩下的脚本。

所以这次,如果我们正在做 A=2 B=2 match=5; seq 5 | sed...

:PRint 的第一次迭代的模式空间如下所示:

^1\n2\n3$

这就是sed收集其$Before缓冲区的方式。因此sed已收集的输入后面打印以输出$B-count行。这意味着,在我们之前的示例中,将反复打印输出,然后将其删除并将一个模式空间发送回脚本顶部,如下所示:sedP1D

^2\n3$

...并且在脚本顶部N检索ext输入行,因此下一次迭代如下所示:

^2\n3\n4$

因此,当我们找到5输入中的第一个匹配项时,模式空间实际上看起来像:

^3\n4\n5$

然后Delete循环开始,它通过时看起来像:

^5$

N扩展输入线被拉动时,sedEOF退出。到那时,它只P清洗了第1行和第2行。

这是一个示例运行:

A=8 B=7 match='[24689]0'
seq 100 |
sed -ne:t -e"/\n.*$match/D" \
    -e'$!N;//D;/'"$match/{" \
            -e"s/\n/&/$A;t" \
            -e'$q;bt' -e\}  \
    -e's/\n/&/'"$B;tP"      \
    -e'$!bt' -e:P  -e'P;D'

打印:

1
2
3
4
5
6
7
8
9
10
11
12
29
30
31
32
49
50
51
52
69
70
71
72
99
100

我实际上正在处理巨大的文件,但答案显然比此解决方案要慢。最初我很犹豫要更改我接受的答案,但是速度差异是显而易见的。
Amelio Vazquez-Reina

4
@Amelio-它将与任何大小的流一起使用,并且无需通读文件即可工作。最大的性能因素是$A和/或的大小$B。您将这些数字越大,获得的速度就越慢-但您可以将它们设置得相当大。
mikeserv

1
@ AmelioVazquez-Reina-如果您使用的是旧款,那我认为更好。
mikeserv

11

您可以gnu grep-A-B一起使用,以精确打印要排除的文件部分,但可以添加-n开关以打印行号,然后格式化输出并将其作为命令脚本传递sed以删除这些行:

grep -n -A1 -B2 PATTERN infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile

这也应该与通过以下方式传递给模式的文件一起grep使用-f

grep -n -A1 -B2 -f patterns infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile

我认为,如果将任何三个或更多连续行号折叠到范围内以使其具有例如2,6d而不是2d;3d;4d;5d;6d...,则可能会略有优化,尽管如果输入只有几个匹配项,则不值得这样做。


其他不保留行顺序并且最有可能变慢的方式:
使用comm

comm -13 <(grep PATTERN -A1 -B2 <(nl -ba -nrz -s: infile) | sort) \
<(nl -ba -nrz -s: infile | sort) | cut -d: -f2-

comm需要排序的输入,这意味着行顺序将不会保留在最终输出中(除非您的文件已经排序),因此nl用于在排序前对行进行编号,comm -13仅打印第二个FILE唯一的行,然后cut删除由nl(即第一个字段和定界符:),
其中包含join

join -t: -j1 -v1 <(nl -ba -nrz -s:  infile | sort) \
<(grep PATTERN -A1 -B2 <(nl -ba -nrz -s:  infile) | sort) | cut -d: -f2-

谢谢唐!简短的问题,您是否希望解决方案的解决方案comm比使用sed和的原始解决方案更快grep
Amelio Vazquez-Reina

1
@ AmelioVazquez-Reina-我不这样认为,因为它仍然会读取输入文件两次(加上它会进行一些排序),而不是Mike的解决方案只处理一次文件。
don_crissti 2015年

9

如果您不介意使用vim

$ export PAT=fff A=1 B=2
$ vim -Nes "+g/${PAT}/.-${B},.+${A}d" '+w !tee' '+q!' foo
aaa
bbb
ccc
hhh
iii
  • -Nes打开不兼容的无声ex模式。对于脚本编写很有用。
  • +{command}告诉vim {command}在文件上运行。
  • g/${PAT}/-在所有匹配的行上/fff/。如果模式包含您不打算以这种方式处理的正则表达式特殊字符,则这将变得棘手。
  • .-${B} -从这一行上方的1行开始
  • .+${A}-到该行下方的2行(请参阅:he cmdline-ranges这两行)
  • d -删除行。
  • +w !tee 然后写入标准输出。
  • +q! 退出而不保存更改。

您可以跳过变量并直接使用模式和数字。我仅出于明确目的使用它们。


3

怎么样(使用GNU grepbash):

$ grep -vFf - file.txt < <(grep -B2 -A1 'fff' file.txt)
aaa
bbb
ccc
hhh
iii

在这里,我们找到了要丢弃的行grep -B2 -A1 'fff' file.txt,然后将其用作输入文件以查找所需的行以将其丢弃。


嗯,这在我的机器(OS X)上什么都没输出
Amelio Vazquez-Reina 2015年

@ AmelioVazquez -雷纳抱歉that..i不知道你的OS before..anyway我已经测试了这个在Ubuntu ..
heemayl

2
这将与kos(现在已删除的)解决方案具有相同的问题,好像输入文件中存在重复的行,其中一些行超出了范围,而其他行在该范围内时,这将全部删除。另外,如果多次出现pattern,则如果--输入文件中有行(超出范围),这将删除它们,因为当有多行匹配pattern时,定界符--出现在grep的输出中(后者不太可能但值得提到我猜)。
don_crissti

@don_crissti Thanks..you被right..although我正在OP的例子literally..i上午会离开它,以防有人发现它的帮助以后..
heemayl

1

通过使用临时文件,您可以达到良好的效果:

my_file=file.txt #or =$1 if in a script

#create a file with all the lines to discard, numbered
grep -n -B1 -A5 TBD "$my_file" |cut -d\  -f1|tr -d ':-'|sort > /tmp/___"$my_file"_unpair

#number all the lines
nl -nln "$my_file"|cut -d\  -f1|tr -d ':-'|sort >  /tmp/___"$my_file"_all

#join the two, creating a file with the numbers of all the lines to keep
#i.e. of those _not_ found in the "unpair" file
join -v2  /tmp/___"$my_file"_unpair /tmp/___"$my_file"_all|sort -n > /tmp/___"$my_file"_lines_to_keep

#eventually use these line numbers to extract lines from the original file
nl -nln $my_file|join - /tmp/___"$my_file"_lines_to_keep |cut -d\  -f2- > "$my_file"_clean

结果很不错,因为您可以在流程中松开一些缩进,但是如果它是xml或缩进不敏感的文件,那应该不成问题。由于此脚本使用ram驱动器,因此写入和读取这些临时文件的速度与在内存中工作的速度一样快。


1

另外,如果您只想在给定标记之前排除一些行,则可以使用:

awk -v nlines=2 '/Exception/ {for (i=0; i<nlines; i++) {getline}; next} 1'

(格伦·杰克曼(/programming//a/1492538))

通过传递一些命令,您可以获得behaivour之前/之后的行为:

awk -v nlines_after=5 '/EXCEPTION/ {for (i=0; i<nlines_after; i++) {getline};print "EXCEPTION" ;next} 1' filename.txt|\
tac|\
awk -v nlines_before=1 '/EXCEPTION/ {for (i=0; i<nlines_before; i++) {getline}; next} 1'|\
tac

1
精妙,awk当您打算影响之前的行并重新反转结果时,请使用反向文件处理以下行。
karmakaze

0

实现此目的的一种方法,也许最简单的方法是创建变量并执行以下操作:

grep -v "$(grep "fff" -A1 -B2 file.txt)" file.txt

这样,您仍然拥有自己的结构。而且,您可以轻松地从一根衬管中看到要去除的东西。

$ grep -v "$(grep "fff" -A1 -B2 file.txt)" file.txt
aaa
bbb
ccc
hhh
iii

与heemayl相同的解决方案,以及与don_crissti描述的相同的问题:这将与kos(现已删除)的解决方案具有相同的问题,好像输入文件中存在重复的行,其中一些行超出了范围,而其他行在该范围内这将全部删除。此外,如果多次出现模式,如果在输入文件(范围之外)中有-行,这会删除它们,因为定界符-当有多行匹配模式时出现在grep的输出中(后者高度匹配)不太可能,但值得一提)。
Bodo Thiesen

0

如果只有1个匹配项:

A=1; B=2; n=$(grep -n 'fff' file.txt | cut -d: -f1)
head -n $((n-B-1)) file.txt ; tail -n +$((n+A+1)) file.txt

否则(awk):

# -vA=a -vB=b -vpattern=pat must be provided
BEGIN{

    # add file again. assume single file
    ARGV[ARGC]=ARGV[ARGC-1]
    ++ARGC
}

# the same as grep -An -Bn pattern
FNR==NR && $0 ~ pattern{
    for (i = 0; i <= B; ++i)
        a[NR-i]++
    for (i = 1; i <= A; ++i)
        a[NR+i]++
}

FNR!=NR && !(FNR in a)
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.