如何 grep 除了匹配项和上一行之外的所有内容

如何 grep 除了匹配项和上一行之外的所有内容

我有一个文本文件,并且有一个我希望 grep 不匹配的模式。问题是,我也希望之前的行不匹配。

我的文件:

line 1
line 2
pattern
line 4

我尝试了cat file | grep -v pattern,它输出:

line 1
line 2
line 4

然后我尝试了cat file | grep -B 1 pattern,输出:

line 2
pattern

然而,当我一起使用它们时cat file | grep -v -B 1 pattern,我得到:

line 2

我怎样才能使输出为:

line 1
line 4

答案1

我倾向于只grep在从文件中提取单行时使用,因此当我需要在文本中执行更复杂的编辑时,我会使用其他工具。

这里的所有解决方案都假设该模式可能在文本中出现多次,并将删除出现该模式的行以及紧邻它们之前的行。如果模式在连续行上匹配,前两个解决方案将会出现问题。


您可以使用sed来匹配模式/pattern/并让它触发命令Nand d,它将下一行追加到缓冲区,然后丢弃两者:

sed '/pattern/ { N; d; }' file

因为你想丢弃该行为了匹配模式,我们将数据向后输入sed,从最后一行开始并向文件开头移动。sed完成后我们再次反转数据。

tac file | sed '/pattern/ { N; d; }' | tac

tac实用程序是 GNU coreutils 的一部分。大多数非 GNU 系统可以使用tail -r来代替tac(检查你的tail(1)手册)。

如果模式匹配两个连续行,则这将无法删除第一行之前的行(因为第一行将被删除)。


使用ed编辑器:

printf '%s\n' 'g/pattern/ -1,. d' ,p Q | ed -s file

这会将命令应用于g/pattern/ -1,. d文件的内容。此命令搜索与 匹配的每一行pattern,然后删除该行及其前面的行。

最后的,p编辑Q命令打印整个文件并退出编辑器而不保存。

如果模式匹配两个连续行,则在删除第一行之前的行后,这将删除第二行之前的行。

(最后一句话是正确的当我写的时候,但这显然是一个只写的句子。)


我们还可以使用grep其非标准但常用的-B选项来为我们提供需要删除的行号。这些数字可以转换为sed我们在原始数据上运行的脚本:

grep -n -B1 'pattern' file | sed 's/[:-].*/d/' | sed -f /dev/stdin file

grep根据问题中的文本,该命令将输出

2-line 2
3:pattern

...第一个sed命令将其转换为sed编辑命令,2d然后是3d(“删除第 2 行和第 3 行”)。sed管道中的最后一个命令采用此编辑脚本并将其应用到原始文本。

此变体不存在与模式匹配的连续行的问题,因为它使用一种 2-pass 方法,首先找到应删除的所有行,然后删除它们(而不是在第一次阅读文本时删除行)。

答案2

将任何 awk 与 tac 一起使用,您可以删除匹配模式之前的任意数量的行:

$ tac file | awk '/pattern/{c=2} !(c&&c--)' file | tac
line 2
line 1

只需更改c=2c=5或任意要删除的行数(最多包括匹配行),例如删除包含数字 97 及其之前的 94 行的行:

$ seq 100 | tac | awk '/97/{c=95} !(c&&c--)' | tac
1
2
98
99
100

现在尝试使用 sed 而不是 awk 来完成此操作:-)。

使用 sed-或 awk-a-line-follow-a-matching-pattern 进行打印对此和其他相关习语的解释。

答案3

笔记:仅当 中file不存在与 的输出匹配的重复行或每行的子字符串时,此代码才有效grep -B1 pattern file

例如,如果file包含以下行:

line 1
line 2
line 2
pattern
line 1 line 2
line 3

而且我使用的grep -B1 pattern file | grep -v "$(cat)" file输出不会如你所料:

line 1
line 3

解决这个问题的最好方法是使用拘萨罗南达的回答

解决方案(这仅适用于没有重复行或子字符串的情况,正如我上面所解释的)

这对bash我有用(我认为有更好的方法):

grep -B1 pattern file | grep -v "$(cat)" file

zsh上面的命令中将不起作用。我不知道为什么。但你可以使用:

grep -B1 pattern file | { val="$(cat)" ; grep -v "$val" file; }

聚苯乙烯你不必使用cat your_file | grep pattern它,这是多余的。你应该使用grep pattern your_file

答案4

您可以使用pcregrep及其Multiline 模式:

pcregrep -Mv '\n.*pattern'

请注意,如果第一行与模式匹配,则不会将其删除。这可以通过使用来解决:

pcregrep -Mv '(\n)?.*pattern'

(...)周围\n显然是必要的,我不知道为什么它不能与8.39 版本一起使用)\n?.*pattern[\n]?.*pattern

相关内容