使用sed或awk打印匹配模式后的下一行

82

问题:我想在包含匹配模式的行后直接打印一行。

我的版本的sed不支持以下语法(它会在+1p处失败),这似乎是一个简单的解决方案:

sed -n '/ABC/,+1p' infile

我认为使用awk进行多行处理会更好,但我不确定如何做。


3
http://sed.sourceforge.net/sed1line.txt - devnull
9个回答

183

在这个上下文中永远不要使用“模式”这个词,因为它很模糊。始终使用“字符串”或“正则表达式”(或在shell中使用“globbing模式”),取决于您真正想要的是什么。有关更多信息,请参见如何查找与模式匹配的文本?

你想要的具体答案是:

awk 'f{print;f=0} /regexp/{f=1}' file

或者专门针对正则表达式的前N个记录(习惯用语“c”如下)进行更一般的解决方案:

awk 'c&&!--c; /regexp/{c=1}' file

以下惯用语描述如何选择给定特定正则表达式匹配的一系列记录:
a)打印所有从某个正则表达式开始匹配的记录:
awk '/regexp/{f=1}f' file

b) 打印出某个正则表达式之后的所有记录:

awk 'f;/regexp/{f=1}' file

c) 在某个正则表达式后打印第N条记录:

awk 'c&&!--c;/regexp/{c=N}' file

d)在某个正则表达式之后打印除第N个记录以外的所有记录:

awk 'c&&!--c{next}/regexp/{c=N}1' file

e) 打印某个正则表达式后的N条记录:

awk 'c&&c--;/regexp/{c=N}' file

f) 打印除了某个正则表达式后的 N 条记录之外的所有记录:

awk 'c&&c--{next}/regexp/{c=N}1' file

g) 打印与某个正则表达式匹配的前N条记录:

awk '/regexp/{c=N}c&&c--' file

我将变量名从“f”更改为“c”,表示“count”,这更能表达变量实际的含义。

f是“found”的缩写。它是一个布尔标记,当我在输入中找到与正则表达式regexp匹配的字符串时(/regexp/{f=1}),我将其设置为1(真)。在脚本的另一个位置,您会看到每个单独的f都被测试为条件,并且当为真时,awk将执行其默认操作以打印当前记录。因此,只有在我们看到regexp并将f设置为1 / true后,才会输出输入记录。

c && c-- { foo }意味着“如果c非零,则将其减少,并且如果仍然为非零,则执行foo”,因此如果c从3开始,则它将递减为2,然后执行foo,在下一行输入中c现在为2,因此它将递减为1,然后再次执行foo,在下一行输入中c现在为1,因此它将递减为0,但是这次不会执行foo,因为0是一个假条件。我们执行c && c--而不是仅测试c-- > 0,这样我们就不会遇到一个巨大的输入文件,其中c减少到0并且继续递减,以至于它再次变成正数。


我正在使用“c)在某个正则表达式之后打印第N条记录”来总结测试结果(即测试失败详细信息是以“●”开头的测试套件名称两行之后),但我注意到有些情况下,如果第二行匹配一个字符串,则需要改为打印下面4行。是否有办法解决这个问题,或者值得再提一个问题? - AncientSwordRage
@AncientSwordRage,提出另一个问题总是比试图在评论中获得答案更值得一试。 - Ed Morton
我想确保我没有错过什么非常明显的东西。既然不是,我就在 https://dev59.com/sXwQtIcB2Jgan1znVOe- 上提问了。 - AncientSwordRage

54

你感兴趣的是匹配后面的那行,对吗?在sed中,可以通过以下方式实现:

sed -n '/ABC/{n;p}' infile

或者,grep的A选项可能是您正在寻找的内容。

-A NUM, Print NUM lines of trailing context after matching lines.
例如,给定以下输入文件:

For example, given the following input file:

foo
bar
baz
bash
bongo

您可以使用以下内容:

$ grep -A 1 "bar" file
bar
baz
$ sed -n '/bar/{n;p}' file
baz

1
注意: {n;p} 似乎由 GNU sed 支持,但 BSD sed 不支持。(感谢 chooban 提供 sed 答案。我非常尊敬 awk,并且已经使用它了,但我尽可能避免重新学习其复杂的语言。(当我需要 awk 时,我使用 perl)。) - Mars
7
更正:我使用 BSD 版本的 sed,通过添加一个 ; 取得了成功:sed -n /bar/{n;p;}。这个命令在 GNU 版本的 sed 中也可以使用。 - Mars
1
使用原始的sed,您必须编写sed -n '/bar/{;n;p;}',因为{}与字母命令完全相同。 - zwol

6

我需要打印出模式(好的Ed,正则表达式)之后的所有行,所以我采用了这个方法:

sed -n '/pattern/,$p' # prints all lines after ( and including ) the pattern

但是因为我想要打印出所有在括号之后的行(并且排除匹配的模式)。

sed -n '/pattern/,$p' | tail -n+2  # all lines after first occurrence of pattern

我想在你的情况下,你可以在末尾添加head -1

sed -n '/pattern/,$p' | tail -n+2 | head -1 # prints line after pattern

我确实应该在这个答案中包含tlwhitec的评论(因为他们的sed-strict方法比我的建议更优雅):

sed '0,/pattern/d' 

上面的脚本会删除从第一行开始到匹配模式的那一行(包括该行)之间的所有行。之后的所有行都会被打印出来。

2
你的第二种情况有一种严格的sed方式:sed '0,/regex/d' - tlwhitec
这就是我建议人们永远不要使用范围表达式 (/start/,/end/) 的原因,这意味着你不能使用 sed 来完成此类任务,因为它没有变量,所以你只能使用范围。你想要做的只是排除起始或结束行,而这需要你添加管道和额外的命令来完成,与 awk '/start/{f=1} f; /end/{f=0}' 相比 - 只需重新排列块以打印或不打印开始/结束部分,不需要额外的工具或管道。 - Ed Morton

3

awk版本:

awk '/regexp/ { getline; print $0; }' filetosearch

1
谢谢!我忘记了grep中的-A选项;它与+1参数完美配合(匹配模式的行不会被打印)。 - user1537723
1
当你最不希望它失败时,这将以难以理解的方式失败,并且在未来很难进行增强。在决定使用getline之前,请确保您已准备好并完全理解http://awk.info/?tip/getline。 - Ed Morton

1
如果模式匹配成功,将下一行复制到模式缓冲区,删除一个回车符,然后退出——副作用是打印。
sed '/pattern/ { N; s/.*\n//; q }; d'

1
“q”绝不是GNU扩展功能。它是一个标准的“sed”命令。 - tripleee
如果您想打印所有匹配项而不仅仅是第一个匹配项,请使用 p 而不是 q - jarno

1
这可能适用于您(GNU sed):
sed -n ':a;/regexp/{n;h;p;x;ba}' file

使用sed类似于grep的选项-n,如果当前行包含所需的正则表达式,则替换当前行为接下来的一行,将该行复制到保留空间(HS),打印该行,交换模式空间(PS)和HS并重复执行。

1

实际上,如果 pattern 匹配 连续 行,则 sed -n '/pattern/{n;p}' filename 将失败:

$ seq 15 |sed -n '/1/{n;p}'
2
11
13
15

预期的答案应该是:

预期答案应该是:

2
11
12
13
14
15

我的解决方案是:

$ sed -n -r 'x;/_/{x;p;x};x;/pattern/!s/.*//;/pattern/s/.*/_/;h' filename

例如:

$ seq 15 |sed -n -r 'x;/_/{x;p;x};x;/1/!s/.*//;/1/s/.*/_/;h'
2
11
12
13
14
15

解释如下:

解释:

  1. x;:在输入的每一行开头,使用x命令交换模式空间保留空间中的内容。
  2. /_/{x;p;x};:如果模式空间(实际上是保留空间)包含_(这只是一个指示符,表示上一行是否匹配了模式),则使用x当前行的实际内容交换到模式空间中,使用p打印当前行,并使用x恢复此操作。
  3. x:恢复模式空间保留空间中的内容。
  4. /pattern/!s/.*//:如果当前行不匹配模式,也就是说我们不应该打印下面的行,则使用s/.*//命令删除模式空间中的所有内容。
  5. /pattern/s/.*/_/:如果当前行匹配模式,也就是说我们应该打印下面的行,则需要设置一个指示符告诉sed打印下一行,因此使用s/.*/_/模式空间中的所有内容替换为_(第二个命令将使用它来判断上一行是否匹配模式)。
  6. h:用模式空间中的内容覆盖保留空间;然后,保留空间中的内容为^_$,这意味着当前行模式匹配,或者为^$,这意味着当前行不匹配模式
  7. 第五步和第六步不能交换,因为在s/.*/_/之后,模式空间无法匹配/pattern/,因此必须执行s/.*//

0
如果您只想要模式后的下一行,这个sed命令将起作用。
sed -n -e '/pattern/{n;p;}'

-n 抑制输出(静默模式); -e 表示 sed 命令(在本例中不需要); /pattern/ 是一个正则表达式搜索,用于查找包含字符组合 pattern 的行(对于仅由“pattern”组成的行,请使用 /^pattern$/); n 用下一行替换模式空间; p 打印;

例如:

seq 10 | sed -n -e '/5/{n;p;}'

请注意,上述命令将在每个包含pattern的行后打印一行。如果您只想要第一个,请使用sed -n -e '/pattern/{n;p;q;}'。这也更有效率,因为不需要读取整个文件。
这个严格的sed命令将打印出模式后的所有行。
sed -n '/pattern/,${/pattern/!p;}

如果以sed脚本格式化,它将是这样的:

/pattern/,${
    /pattern/!p
}

这里是一个简短的示例:

seq 10 | sed -n '/5/,${/5/!p;}'

/pattern/,$ 会选择从 pattern 到文件结尾的所有行。

{} 分组下一组命令(类似 C 语言块命令)

/pattern/!p; 打印不匹配 pattern 的行。请注意,在早期版本和某些非 GNU 版本的 sed 中需要使用 ;。这将把指令转换为排除范围 - sed 范围通常对范围的起始点和结束点都是包容的。

要排除范围的末尾,您可以像这样做:

sed -n '/pattern/,/endpattern/{/pattern/!{/endpattern/d;p;}}

/pattern/,/endpattern/{
    /pattern/!{
        /endpattern/d
        p
    }
}

/endpattern/d 从“模式空间”中删除,脚本从顶部重新启动,跳过该行的p命令。

另一个简洁的例子:

seq 10 | sed -n '/5/,/8/{/5/!{/8/d;p}}'

如果您有GNU sed,您可以添加调试开关:
seq 5 | sed -n --debug '/2/,/4/{/2/!{/4/d;p}}'

输出:

SED PROGRAM:
  /2/,/4/ {
    /2/! {
      /4/ d
      p
    }
  }
INPUT:   'STDIN' line 1
PATTERN: 1
COMMAND: /2/,/4/ {
COMMAND: }
END-OF-CYCLE:
INPUT:   'STDIN' line 2
PATTERN: 2
COMMAND: /2/,/4/ {
COMMAND:   /2/! {
COMMAND:   }
COMMAND: }
END-OF-CYCLE:
INPUT:   'STDIN' line 3
PATTERN: 3
COMMAND: /2/,/4/ {
COMMAND:   /2/! {
COMMAND:     /4/ d
COMMAND:     p
3
COMMAND:   }
COMMAND: }
END-OF-CYCLE:
INPUT:   'STDIN' line 4
PATTERN: 4
COMMAND: /2/,/4/ {
COMMAND:   /2/! {
COMMAND:     /4/ d
END-OF-CYCLE:
INPUT:   'STDIN' line 5
PATTERN: 5
COMMAND:     /2/,/4/ {
COMMAND:     }
END-OF-CYCLE:

0

通过管道连接一些grep命令就可以实现它(它在POSIX shell和BusyBox下运行):

cat my-file | grep -A1 my-regexp | grep -v -- '--' | grep -v my-regexp
  1. -v 选项会显示不匹配的行
  2. -- 是 grep 打印的用于分隔每个匹配项的标记,因此我们也跳过它

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接