【问题标题】:How, using sed, can one extract a regex-delimited range except for the last line?除了最后一行,如何使用 sed 提取正则表达式分隔的范围?
【发布时间】:2016-08-19 10:36:33
【问题描述】:

一个简单的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。

【问题讨论】:

  • 请参阅 How to select lines between two patterns? 并为此提供 sed 和 awk 解决方案。
  • 这很有趣,但它重复了正则表达式。
  • 所以 不重复 end-regex 你的意思是用 end-regex 只写一次的方式编写 sed 命令,对吧?
  • 是的,我就是这个意思。
  • vim 允许使用/pat1/+1,/pat2/-1,这取决于光标所在的位置和使用的模式。在 sed 中使用类似的模式肯定会很好

标签: sed


【解决方案1】:

永远不要出于这个原因使用范围(考虑到最轻微的需求变化,它们需要重写或复制条件)。改用标志:

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

【讨论】:

  • 使用awk很难满足问题的要求。
  • 如果第二个表达式也出现在第一个表达式之前和/或它是第一个表达式的子字符串,则会中断。这与 Jonathan Leffler 提供的 sed 示例之间的主要区别在于 sed 模式定义了一个范围,而 awk 则没有。 (虽然答案是 OT,但我确实认为您的贡献很有趣)。
  • 请注意,乔纳森的回答并没有按照您的要求做,即省略结束模式。在第一个表达式之前出现的第二个表达式的效果为零(尝试一下),如果第二个表达式是第一个表达式的子字符串,它不会中断,您只需要编写正确的表达式,例如/^7$/。如果您发布了一个您认为此模式不适用的示例,我将向您展示如何正确编写它。
  • @EdMorton - 你是对的。不得不吃我的话。一直很享受尝试使用 sed 对 MacGyver 进行某些操作的机会,但是....就是做不到。
  • 哦,我也收回了。正如你所预料的那样,我想出了一个完全愚蠢的方法来强迫 sed 这样做。哈!
【解决方案2】:

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 中完成。

【讨论】:

  • +1 表示坚持sed。如果表达式需要使用双引号来表达,您是否知道表达! 的简洁方法?我已经这样做了:sed -n -e "/first-regex/,/second-pattern/ { //"'!"p; }"(我知道最后没有必要回到双引号,但我想说明这个问题)。不过我喜欢你的回答——它简洁明了。
  • 对我来说,set +H 创造了奇迹,禁用了我从不使用的历史记录。如果我想要 C shell,我会使用它!这在脚本(非交互式 shell)中不是问题。在命令行中,我可能会使用您正在使用的变体,在范围部分使用双引号,在其余部分使用单引号。您还可以在感叹号前使用反斜杠来禁止历史扩展。
  • 如果我有变量保存模式,那么重复模式不是问题;我只是再次引用该变量。
  • @starfry 从不将任何脚本括在双引号中,始终是单引号。如果您需要在任何脚本中访问 shell 变量的值,那么语法是 cmd 'foo '"$var"' bar',而不是 cmd "foo $var bar"
  • 感谢@edmorton 的建议,我会调整我的行为:)
【解决方案3】:

下面的第二个示例是仅 sed 的答案,它用空行填充输出。第三个示例准确地给出了所要求的内容,前提是您可以选择一个永远不在应保留范围内的模式。

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

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

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

但是,如前所述,这仅在范围出现一次时才有效。如果范围出现多次,匹配end-regex 的行仍然在保持空间中。

因此,下面的脚本会清空 超出 范围的行,用h 填充保留空间中的空行,然后运行x;p,这将打印一个空行start-regexend-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

【讨论】:

  • idk 如果这真的适用于您使用 OMITME 所做的事情(我的口味太多神秘符文),但仅供参考,您可以创建占位符字符串 aB使用 sed:sed 's/a/aA/g; s/something/aB/g; do_stuff; s/aB/something/g; s/aA/a/g' 保证输入中不存在惯用的。申请和解释见stackoverflow.com/a/38153467/1745001
  • @EdMorton - 当然......只是变得更加复杂。哦,我忘了提到,因为我要删除范围之外每一行的内容,这当然不适用于具有多个 /start/,/end/ 范围的脚本。
猜你喜欢
  • 2010-10-31
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多