【问题标题】:Find and print two blocks of lines from file with sed in one pass使用 sed 一次性从文件中查找并打印两行块
【发布时间】:2019-06-04 05:12:44
【问题描述】:

我正在尝试使用 sed 命令从如下所示的文本文件中查找并打印两个可变行数的块:

...
INFO first block to match
id: "value"
...
last line of the first block
INFO next irrelevant block
id: "different value"
...
INFO second block to match
id: "value"
...
last line of the second block
...

我只知道 id 值以及每个块都以具有“INFO”的行开头的事实。我想匹配第一行中的每个块,而不是在输出中包含下一个块的第一行:

INFO first block to match
id: "value"
...
last line of the first block

INFO second block to match
id: "value"
...
last line of the second block

理想情况下,我宁愿一次性完成,而不是从上到下多次扫描文件。目前我有这个(它只匹配第一个块,我需要两个):

sed -n -e "/INFO/{"'$!'"{N;/INFO.*id: \"value\"/{:l;p;n;/^[^\\[]/bl;}}}" file.log

编辑

块之间的换行当然很好,但完全是可选的。

编辑 2

请注意INFOid: "value" 不必在行首,而且我的示例中的所有其他词都是任意的并且事先不知道。在我需要匹配的块之间和周围可以有任意数量的块(包括 0 个)。

【问题讨论】:

  • 您的意思是您只想提取 ID?请通过编辑问题提供所需输出的示例。
  • awk 是完成此类任务的更好工具。使用 sed 进行简单的替换,仅此而已。
  • @oguz ismail awk 命令会是什么?
  • 这样awk '/^INFO/{p=0;a=$0;next} $0=="id: \"value\""{print a;p=1} p' file
  • 您的“EDIT 2”本质上是说您提供的示例不能充分满足您的要求。修复示例,否则我们没有可以测试潜在解决方案以查看它是否有效的东西。您在下面有多个答案,并且您在所有答案下方的 cmets 中为您的示例问题再次道歉 - 只需修复示例即可。最后,sed 用于对单个字符串执行 s/old/new - 这不是您正在执行的操作,因此您不应该考虑使用 sed。

标签: sed


【解决方案1】:

sed 功能强大、简洁而愚蠢。 awk 更聪明!

awk '/^INFO/{f = /match/? 1: 0} f'

edit: 我看到你想在每个“块”之间换行;如果我找到更严格的方法会更新:

awk '/^INFO/{f = /match/? 1: 0; if(i++) $0 = RS $0} f'
  • /^INFO/{action}:仅在以“INFO”开头的行上执行{action}
  • variable = if ? then : else: Conditional Expression(三元运算符)
  • if(i++):第一次计算时,i 将为零,因此表达式将为假。这可以防止在第一个块中出现额外的换行符。

  • $0 = RS $0:将记录分隔符(换行符)添加到$0(整个记录)

  • f 如果 f 大于零,则暗示 {print $0}

【讨论】:

  • 这非常简洁。不幸的是,它在我的情况下效果不佳。请参阅我的问题中的“编辑 2”部分。抱歉,如果我的示例具有误导性。
【解决方案2】:

这可能对你有用(GNU sed):

sed -nE ':a;/^INFO/{N;/^id: "value"/M!D;:b;H;$!{n;/^INFO/!bb};x;s/^/x/;/^x{2}/{s/^x*.//p;q};x;ba}' file

此解决方案将所需的块存储在保持空间中,并以计数器为前缀。一旦存储了所需数量的块,就会删除计数器,打印块并退出进程。

该解决方案(仅基于提供的输入)假设id(如果存在)始终跟随INFO 行。

【讨论】:

  • 哇,真是个怪物!哦,伙计,我愚蠢地认为我在 sed 周围有些自在:D
  • 这几乎可以工作,除了它也输出第二个块之后的行。我想阻止它,特别是因为其中一些块后面是某种清除我的终端的控制字符序列。你介意修改它来解决这个问题,并允许将 INFO 和 id 放置在行上的任意位置吗?
  • 你的命令在块之间输出一个换行符,这很好,但不是绝对必要的,我在用手机编辑问题时不小心把它放在示例输出中,我的错。
  • @dols3m 我已经删除了块之间的空行,但是对于 EDIT2?
【解决方案3】:

这里是使用sedawk 组合的替代解决方案。它允许您按块或按记录解析输入。这种方法依赖于将 awk 记录分隔符 (RS) 设置为空字符串,这使得 awk 一次读取一个完整的块。

所以有2个步骤:

  1. 使输入记录可解析。
  2. 处理每条记录。

对于你的例子,这可能是这样的:

sed '1!s/^INFO/\n&/' infile | awk '/id: "value"/' RS= ORS='\n\n'

输出:

INFO first block to match
id: "value"
...
last line of the first block

INFO second block to match
id: "value"
...
last line of the second block

【讨论】:

  • 您介意为我在 EDIT 2 部分中提到的内容更新它吗?
  • @dols3m:以什么方式?我显然没有具有代表性的数据样本。请阅读并遵守instructions 使您的问题符合 MCVE 标准
【解决方案4】:

awk 对此很有用,如果您可以将 RS 设置为多字符表达式,那将是理想的选择。 (gnu awk 允许这样做,但是当有 perl 时,为什么还要使用 gnu awk 呢?)

perl -wnle 'BEGIN{$/="INFO"; undef $\} print "$/$_" if m/id: \"value\"/' input

基本上,这会将记录分隔符 ($/) 设置为字符串“INFO”(所以现在您的每个“记录”都是 perl 的“行”)。如果记录与模式id: "value" 匹配,则会在开头打印“INFO”。 (没有-n,perl 会在每条记录的末尾保留记录分隔符,这不是您想要的)。通过省略“undef $\”,您可以获得记录之间的额外换行符。一些代码高尔夫可能会将其长度减半,但我的 perl 有点生疏了。等待 cmets 中的较短版本。

【讨论】:

  • 整洁,但 INFO 可能不在行首,我仍然希望从头开始打印该行。
【解决方案5】:

这可能是也可能不是您想要的,具体取决于您的真实数据的样子:

$ awk '/INFO/{info=$0; f=0} /id: "value"/{print info; f=1} f' file
INFO first block to match
id: "value"
...
last line of the first block
INFO second block to match
id: "value"
...
last line of the second block

或者如果你想对每个块做更多的事情,而不是在你去的时候打印它,那么一些变化会更好:

$ awk '
    /INFO/ { prt() }
    { block = block $0 ORS }
    END { prt() }
    function prt() {
        if (block ~ /id: "value"/) {
            printf "%s", block
        }
        block=""
    }
' file
INFO first block to match
id: "value"
...
last line of the first block
INFO second block to match
id: "value"
...
last line of the second block

在任何 UNIX 机器上的任何 shell 中使用任何 awk 时,上述行为都会相同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-15
    • 2014-03-09
    • 1970-01-01
    • 2013-01-27
    • 2022-10-12
    • 2017-01-12
    • 2016-06-12
    • 2015-03-06
    相关资源
    最近更新 更多