使用sed如何提取除最后一行之外的正则表达式分隔范围?

时间:2016-08-19 10:36:34

标签: sed

一个简单的sed表达式,用于从文本文件中提取由正则表达式分隔的行块,如下所示:

$ sed -n -e '/start-regex/,/end-regex/ p' input_file

这会选择与start-regex匹配的行,包括匹配end-regex的行。

匹配end-regex的行可能会被排除在外:

$ sed -n -e '/start-regex/,/end-regex/ {/end-regex/d;p}

是否可以在不重复end-regex的情况下执行此操作?

如果可以省略最后一行,那么它是否可以省略第一行和/或最后一行而不重复正则表达式?

这个问题的原因是找到一种更有效的方法来解决问题,而不是重复那些复杂且难以阅读的表达。

这个问题是关于sed,特别是其中的一个实例。可能有一些方法可以使用headtailawk等管道执行此操作,但问题是仅使用sed询问是否可行。

有许多类似的问题,但他们要求针对特定用例的解决方案,而不是从源头处理一般问题。

任何解决方案都应该适用于GNU sed。

3 个答案:

答案 0 :(得分:3)

不要因为这个原因而使用范围(在最轻微的要求变化时,他们需要重写或重复条件)。改为使用标志:

awk '/start/{f=1} /end/{f=0} f' file

这意味着你不能用sed以任何简洁,可移植的方式做到这一点(可能有一些奇怪的单字符符文组合可以在GNU sed中做你想要的但是如果你认为重复这个条件是complex and hard to read等到你看到了!),你需要一个像awk这样支持变量的工具。使用上述方法,您可以通过重新排列脚本的3个部分从所有分隔符打印到任何分隔符(添加{print}只是为了清晰而不依赖于默认行为):

$ seq 1 10 | awk '/3/{f=1} f{print} /7/{f=0}'
3
4
5
6
7

$ seq 1 10 | awk 'f{print} /3/{f=1} /7/{f=0}'
4
5
6
7

$ seq 1 10 | awk '/3/{f=1} /7/{f=0} f{print}'
3
4
5
6

$ seq 1 10 | awk '/7/{f=0} f{print} /3/{f=1}'
4
5
6

答案 1 :(得分:1)

BSD和GNU sed都同意你可以省略范围中的第一行和最后一行而不重复任何正则表达式,但它有点古怪。

sed -n -e '/first-regex/,/second-pattern/ { //!p; }'

(BSD sed需要分号; GNU sed并不介意是否存在分号。)

空正则表达式//匹配最后匹配的正则表达式,在此上下文中,它是第一个模式(在范围的开头)或第二个模式(在范围的结尾) 。请注意,如果有多个此类范围,则范围应该是不相交的。

给定一个名为data的输入文件(我碰巧在另一个问题上玩这个):

0x0  = 0
0x1  = 1
0x2  = 2
0x3  = 3
0x4  = 4
0x5  = 5
0x6  = 6
0x7  = 7
0x8  = 8
0x9  = 9
0xA  = 0
0xB  = 11
0xC  = 12
0xD  = 13
0xE  = 14
0xF  = 15

你可以跑:

$ sed -n -e '/0x4/,/0xC/ { //!p; }' data
0x5  = 5
0x6  = 6
0x7  = 7
0x8  = 8
0x9  = 9
0xA  = 0
0xB  = 11
$

我还没有找到一种方法来省略两种模式中的一种(开始或结束模式)而不是两种模式。我怀疑,如果不重复一个或另一个正则表达式,就无法在sed中完成。

答案 2 :(得分:0)

下面的第二个示例是一个仅使用sed的答案,用空行填充输出。第三个例子给出了所要求的内容,前提是你可以选择一个永远不应该保留的模式。

如果在输入文件中,范围仅匹配一次,则此方法有效。它以空行打印您想要的内容。

sed -n -e '/start-regex/,/end-regex/{x;p}' input-file

对于范围中的每一行,x将模式空间中的线与保留空间中的线交换,p打印从保留空间拉出的线。这有效地打印了每一行。

但是,如上所述,只有在范围发生一次时才有效。如果范围出现多次,则匹配end-regex的行仍在保留空间中。

相反,下面的脚本清空了范围之外的行,在h的保留空间中清空行,然后运行x;pstart-regex打印一个空白行,为end-regex打印空白行:

sed -n -e '/start-regex/,/end-regex/! {s/.//g;h;};x;p' ' input-file

以上,是我能给予的最普遍的。它保留了该范围内的空白行,但不是一个完美的解决方案,因为它在范围之前插入空行:


start-regex line 1
  next line is blank...
etc1
start-regex line 2 etc2

要删除空白行,您可以将最终p更改为/^$/! p,但这将省略输入文件范围内的空白行以及脚本在每个范围之前添加的填充行。如果确实无法添加空行,您可以始终在不匹配的行中插入占位符:

sed -n -e '/start-regex/,/end-regex/! {s/.*/OMITME/;h;};x;/OMITME/! p' ' input-file

这仍然取决于OMITME不是你要保留的范围内的模式。但是你得到了理想的结果:

start-regex line 1
  next line is blank...

  etc1
start-regex line 2
  etc2