【问题标题】:How to extract lines between multiline patterns?如何提取多线模式之间的线?
【发布时间】:2016-12-19 13:33:03
【问题描述】:

我有一个看起来像这样的文件:

  blah blah blah blah blah blah blah blah 
  blah blah blah blah blah blah blah blah 
  blah blah blah blah blah blah blah blah 
<empty line here>
     Total DOS and NOS and partial (IT) DOSDOWN   
<empty line here>
     E     Total     1
<empty line here>
-1.5000    0.004    0.000    0.004
-1.4953    0.004    0.000    0.004
-1.4906    0.004    0.000    0.004
-1.4859    0.004    0.000    0.004
-1.4812    0.004    0.000    0.004
 0.3563    0.708    5.510    0.708
 0.3609    0.562    5.513    0.562
 0.3656    0.381    5.515    0.381
 0.3703    0.149    5.517    0.149
<empty line here>
     Sublattice  1 Atom Fe   spin DOWN   

我想要的是提取(第一个模式)之间的所有行

     Total DOS and NOS and partial (IT) DOSUP     
<empty line here>    
     E     Total     1
<empty line here>

和(第二个模式)

<empty line here>
     Sublattice  1 Atom Fe   spin DOWN   

即我想得到

-1.5000    0.004    0.000    0.004
-1.4953    0.004    0.000    0.004
-1.4906    0.004    0.000    0.004
-1.4859    0.004    0.000    0.004
-1.4812    0.004    0.000    0.004
 0.3563    0.708    5.510    0.708
 0.3609    0.562    5.513    0.562
 0.3656    0.381    5.515    0.381
 0.3703    0.149    5.517    0.149

所以,归根结底,我希望在两个多线模式之间有线条。 据我了解awk 可以通过状态机检测多行模式(请参阅here),但在我的情况下我没有这样做。

任何如何解决此问题的建议将不胜感激。

【问题讨论】:

  • 第二个模式可以缩减为&lt;empty line here&gt;
  • awk -v RS= 'NR==3' file 将打印第三个以空行分隔的文本块,从而产生您想要的输出 - 有什么理由不能这样做?
  • @EdMorton 好一个。我把它弄得太复杂了……
  • @EdMorton 很好,但是我要查找的文本块被错误放置在一个巨大的文本文件中,只能通过“Total DOS and NOS and partial (IT) DOSUP”这一行来识别...”行“E Total 1”不是唯一的,不能使用。
  • @EdMorton ... 叹息,你又做了一次。为什么我还要在你醒着的时候回答问题? :-D

标签: awk sed


【解决方案1】:

使用sedsed -n '5,/^$/{/^$/d}'

但这假设“多行起始模式”始终位于文件的开头。否则它会变得有点复杂。像这样:

/Total/{N;N;N}
/Total.*Total/,/^$/{
    /Total/d
    /^$/d
}

这里我假设'Total' 匹配多行模式的开头,'Total.*Total' 匹配整个模式。如果有其他模式以多行模式的第一行开头但短于 4 行,则将 N;N;N 替换为更复杂的内容。

【讨论】:

    【解决方案2】:

    从您的 cmets 看来,您所需要的只是:

    awk -v RS= '/Total DOS/{tgt=NR+2} NR==tgt' file
    

    如果不是,请编辑您的问题以澄清。如果您只想要文件输出中的第一个匹配块并且效率是一个问题,请将其设为NR==tgt{print; exit}。如有必要,将正则表达式更改为与 Total DOS... 行一样多,以使其唯一。

    这里是针对您提供的示例输入运行的:

    $ cat file
      blah blah blah blah blah blah blah blah
      blah blah blah blah blah blah blah blah
      blah blah blah blah blah blah blah blah
    
         Total DOS and NOS and partial (IT) DOSUP
    
         E     Total     1
    
      -1.5000    0.004    0.000    0.004
      -1.4953    0.004    0.000    0.004
      -1.4906    0.004    0.000    0.004
      .......    .....    .....    .....
       0.3609    0.562    5.513    0.562
       0.3656    0.381    5.515    0.381
       0.3703    0.149    5.517    0.149
    
       blah      blah     blah     blah
    
    $ awk -v RS= '/Total DOS/{tgt=NR+2} NR==tgt' file
      -1.5000    0.004    0.000    0.004
      -1.4953    0.004    0.000    0.004
      -1.4906    0.004    0.000    0.004
      .......    .....    .....    .....
       0.3609    0.562    5.513    0.562
       0.3656    0.381    5.515    0.381
       0.3703    0.149    5.517    0.149
    

    【讨论】:

    • 您的回答完全正确。我的原始数据的问题是(并且是)Total DOS... 之后的空行之一有一个 invisible 空格符号,因此 awk 不能将其视为空行。
    【解决方案3】:

    这是一个基于 Ed Morton 技巧的解决方案。

    awk -v RS= 'n==2; /Total DOS/ || n {n++;next} {n=0}' input.txt
    

    这是它的工作原理。

    • RS= 将 awk 置于多行模式,以便记录包含行块。
    • n==2; 打印满足此条件时处理的任何记录。
    • /RE/ || n 是一个条件,如果 RE(模式)在当前记录中匹配或变量 n 不为零,则计算结果为 true。
    • {n++;next} 显然会增加 n 并跳到下一条记录。
    • {n=0} 如果我们还没有跳到下一条记录,我们会重置 n

    所有这一切的效果是我们打印在匹配模式的记录之后的两条记录。您当然可以将开始计数器的条件调整为您喜欢的任何条件。 $2=="Total" 例如。盐调味。

    sh-3.2$ cat input.txt
      blah blah blah blah blah blah blah blah
      blah blah blah blah blah blah blah blah
      blah blah blah blah blah blah blah blah
    
         Total DOS and NOS and partial (IT) DOSUP
    
         E     Total     1
    
      -1.5000    0.004    0.000    0.004
      -1.4953    0.004    0.000    0.004
      -1.4906    0.004    0.000    0.004
      .......    .....    .....    .....
       0.3609    0.562    5.513    0.562
       0.3656    0.381    5.515    0.381
       0.3703    0.149    5.517    0.149
    
       blah      blah     blah     blah
    
    sh-3.2$ awk -v RS=  'n==2; /Total DOS and NOS/||n{n++;next} {n=0}' input.txt
      -1.5000    0.004    0.000    0.004
      -1.4953    0.004    0.000    0.004
      -1.4906    0.004    0.000    0.004
      .......    .....    .....    .....
       0.3609    0.562    5.513    0.562
       0.3656    0.381    5.515    0.381
       0.3703    0.149    5.517    0.149
    

    【讨论】:

    • @glanz - 你能澄清一下吗?对我来说,鉴于您问题中的输入数据,这产生了您在“我想要得到”下提到的输出。七行,两块三行四列,由带点的线隔开。没有其他的。您的实际数据是否有可能在模式后有两个空行,而不仅仅是一个?
    • 我认为 {n=0} 块只有在 n 已经是 0 时才会被击中,所以你可以删除它,或者如果你想在之后重置它,可以想出一些其他逻辑打印第一个目标块。
    • @ghoti @Ed - 您的回答完全正确并且解释得很好。我终于意识到为什么它不适用于我的原始数据。问题是(并且是)Total DOS... 之后的空行之一有一个 invisible 空格符号,因此 awk 不能将其视为空行。再次感谢您。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-13
    • 2011-10-29
    • 2012-06-20
    • 2018-06-08
    • 2014-03-13
    • 2020-09-11
    • 1970-01-01
    相关资源
    最近更新 更多