【问题标题】:How to print lines between two patterns, inclusive or exclusive (in sed, AWK or Perl)?如何在两个模式之间打印线,包括或不包括(在 sed、AWK 或 Perl 中)?
【发布时间】:2016-12-22 16:49:21
【问题描述】:

我有一个类似下面的文件,我想打印两个给定模式 PAT1PAT2 之间的行。

1
2
PAT1
3    - first block
4
PAT2
5
6
PAT1
7    - second block
PAT2
8
9
PAT1
10    - third block

我已阅读How to select lines between two marker patterns which may occur multiple times with awk/sed,但我很想知道所有可能的组合,包括或不包括模式。

如何打印两个图案之间的所有线条?

【问题讨论】:

标签: shell perl awk sed pattern-matching


【解决方案1】:

在 PAT1 和 PAT2 之间打印行

$ awk '/PAT1/,/PAT2/' file
PAT1
3    - first block
4
PAT2
PAT1
7    - second block
PAT2
PAT1
10    - third block

或者,使用变量:

awk '/PAT1/{flag=1} flag; /PAT2/{flag=0}' file

这是如何工作的?

  • /PAT1/ 匹配包含此文本的行,/PAT2/ 匹配。
  • 当在一行中找到文本 PAT1 时,/PAT1/{flag=1} 设置 flag
  • 当在一行中找到文本 PAT2 时,/PAT2/{flag=0} 取消设置 flag
  • flag 是具有默认操作的模式,即 print $0:如果 flag 等于 1,则打印该行。这样,它将打印从PAT1 出现到下一个PAT2 出现的所有行。这还将打印从 PAT1 的最后一个匹配项到文件末尾的行。

在 PAT1 和 PAT2 之间打印行 - 不包括 PAT1 和 PAT2

$ awk '/PAT1/{flag=1; next} /PAT2/{flag=0} flag' file
3    - first block
4
7    - second block
10    - third block

这使用next 跳过包含PAT1 的行以避免被打印。

next 的调用可以通过重新洗牌来放弃:awk '/PAT2/{flag=0} flag; /PAT1/{flag=1}' file

在 PAT1 和 PAT2 之间打印行 - 包括 PAT1

$ awk '/PAT1/{flag=1} /PAT2/{flag=0} flag' file
PAT1
3    - first block
4
PAT1
7    - second block
PAT1
10    - third block

通过将flag 放在最后,它会触发在 PAT1 或 PAT2 上设置的操作:在 PAT1 上打印,而不是在 PAT2 上打印。

在 PAT1 和 PAT2 之间打印行 - 包括 PAT2

$ awk 'flag; /PAT1/{flag=1} /PAT2/{flag=0}' file
3    - first block
4
PAT2
7    - second block
PAT2
10    - third block

通过将flag 放在最开始,它会触发之前设置的操作,因此打印结束模式而不是开始模式。

打印 PAT1 和 PAT2 之间的行 - 如果没有其他 PAT2 出现,则不包括从最后一个 PAT1 到文件末尾的行

这是基于a solution by Ed Morton

awk 'flag{
        if (/PAT2/)
           {printf "%s", buf; flag=0; buf=""}
        else
            buf = buf $0 ORS
     }
     /PAT1/ {flag=1}' file

作为单行:

$ awk 'flag{ if (/PAT2/){printf "%s", buf; flag=0; buf=""} else buf = buf $0 ORS}; /PAT1/{flag=1}' file
3    - first block
4
7    - second block

# note the lack of third block, since no other PAT2 happens after it

这会将所有选定的行保存在从找到 PAT1 时开始填充的缓冲区中。然后,它会不断填充以下行,直到找到 PAT2。此时,它会打印存储的内容并清空缓冲区。

【讨论】:

  • 一个有用的代码,我已经打包并上传为#sparrow脚本,以便其他人重用 - sparrowhub.org/info/awk-select-lines
  • 是最短匹配吗?
  • @MukulAnand 视情况而定
  • 如果我想从模式之间的文件中的行中打印一个单词/列怎么办?这是一个答案 echo "n" |百胜更新 | awk '/PAT1/{标志=1;下一个} /PAT2/{flag=0} flag{ print $5 }'
  • 我可以在这个 awk 上做 grep 吗?喜欢:$ awk '/PAT1/,/PAT2/' | grep "XYZ" ?
【解决方案2】:

经典的sed 解决方案怎么样?

在 PAT1 和 PAT2 之间打印行 - 包括 PAT1 和 PAT2

sed -n '/PAT1/,/PAT2/p' FILE

在 PAT1 和 PAT2 之间打印行 - 不包括 PAT1 和 PAT2

GNU sed
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
任何 sed1
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' FILE

甚至(感谢Sundeep):

GNU sed
sed -n '/PAT1/,/PAT2/{//!p}' FILE
任何 sed
sed -n '/PAT1/,/PAT2/{//!p;}' FILE

在 PAT1 和 PAT2 之间打印行 - 包括 PAT1 但不包括 PAT2

以下仅包括范围开始:

GNU sed
sed -n '/PAT1/,/PAT2/{/PAT2/!p}' FILE
任何 sed
sed -n '/PAT1/,/PAT2/{/PAT2/!p;}' FILE

在 PAT1 和 PAT2 之间打印行 - 包括 PAT2 但不包括 PAT1

以下仅包括范围结束:

GNU sed
sed -n '/PAT1/,/PAT2/{/PAT1/!p}' FILE
任何 sed
sed -n '/PAT1/,/PAT2/{/PAT1/!p;}' FILE

1关于 BSD/Mac OS X sed 的注意事项

这样的命令:

sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE

会发出错误:

▶ sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
sed: 1: "/PAT1/,/PAT2/{/PAT1/!{/ ...": extra characters at the end of p command

因此,此答案已被编辑为包括 BSD 和 GNU 版本的单行代码。

【讨论】:

  • 嘿,经典更短!
  • 不确定其他版本,但是使用GNU sed,第一个可以简化为sed -n '/PAT1/,/PAT2/{//!p}' file ... from manual empty regular expression ‘//’ repeats the last regular expression match
  • @Sundeep 这就是提示。 POSIX 说:If an RE is empty (that is, no pattern is specified) sed shall behave as if the last RE used in the last command applied (either as an address or as part of a substitute command) was specified. 看起来这里唯一剩下的问题是如何解释the last RE。 BSD 对此有所说明。看这里(第 23 点):github.com/freebsd/freebsd/blob/master/usr.bin/sed/POSIX
  • 看起来像。很难找到不兼容的版本来证明这一点。 :)
  • @AlexHarvey 我认为这是你在这里所做的善意的一个很好的例子,通过分享你的知识来改进其他答案。最终,这是我发布此问题时的目标,因此我们可以拥有一组规范的 (yet another one :P) 来源。非常感谢!
【解决方案3】:

grep 与 PCRE(如果可用)结合使用以打印标记和标记之间的线条

$ grep -Pzo "(?s)(PAT1(.*?)(PAT2|\Z))" file
PAT1
3    - first block
4
PAT2
PAT1
7    - second block
PAT2
PAT1
10    - third block
  • -P perl 正则表达式,PCRE。并非所有grep 变体
  • -z 将输入视为一组行,每行 以零字节而不是换行符结束
  • -o 仅打印匹配
  • (?s)DotAll,即。 dot 也能找到换行符
  • (.*?)非贪心发现
  • \Z 仅匹配字符串末尾,或末尾换行符之前

在标记之间打印线,不包括结束标记

$ grep -Pzo "(?s)(PAT1(.*?)(?=(\nPAT2|\Z)))" file
PAT1
3    - first block
4
PAT1
7    - second block
PAT1
10    - third block
  • (.*?)(?=(\nPAT2|\Z)) 非贪婪查找与\nPAT2\Z 的前瞻

在标记之间打印不包括标记的线条

$ grep -Pzo "(?s)((?<=PAT1\n)(.*?)(?=(\nPAT2|\Z)))" file
3    - first block
4
7    - second block
10    - third block
  • (?&lt;=PAT1\n)PAT1\n 的正面回溯

在标记之间打印线,不包括开始标记

$ grep -Pzo "(?s)((?<=PAT1\n)(.*?)(PAT2|\Z))" file
3    - first block
4
PAT2
7    - second block
PAT2
10    - third block

【讨论】:

  • 你能解释一下为什么我们需要 (?s) 因为 -z 应该向我“删除”新行。我发现没有它是行不通的,但我不确定我明白为什么......
【解决方案4】:

这是另一种方法

包括两种模式(默认)

$ awk '/PAT1/,/PAT2/' file
PAT1
3    - first block
4
PAT2
PAT1
7    - second block
PAT2
PAT1
10    - third block

屏蔽两种模式

$ awk '/PAT1/,/PAT2/{if(/PAT2|PAT1/) next; print}' file
3    - first block
4
7    - second block
10    - third block

掩码开始模式

$ awk '/PAT1/,/PAT2/{if(/PAT1/) next; print}' file
3    - first block
4
PAT2
7    - second block
PAT2
10    - third block

掩码结束模式

$ awk '/PAT1/,/PAT2/{if(/PAT2/) next; print}' file
PAT1
3    - first block
4
PAT1
7    - second block
PAT1
10    - third block

【讨论】:

    【解决方案5】:

    为了完整起见,这里是一个 Perl 解决方案:

    在 PAT1 和 PAT2 之间打印行 - 包括 PAT1 和 PAT2

    perl -ne '/PAT1/../PAT2/ and print' FILE
    

    或:

    perl -ne 'print if /PAT1/../PAT2/' FILE
    

    在 PAT1 和 PAT2 之间打印行 - 不包括 PAT1 和 PAT2

    perl -ne '/PAT1/../PAT2/ and !/PAT1/ and !/PAT2/ and print' FILE
    

    或:

    perl -ne 'if (/PAT1/../PAT2/) {print unless /PAT1/ or /PAT2/}' FILE 
    

    在 PAT1 和 PAT2 之间打印行 - 仅排除 PAT1

    perl -ne '/PAT1/../PAT2/ and !/PAT1/ and print' FILE
    

    在 PAT1 和 PAT2 之间打印行 - 仅排除 PAT2

    perl -ne '/PAT1/../PAT2/ and !/PAT2/ and print' FILE
    

    另见:

    • perldoc perlop 中的范围运算符部分,了解有关 /PAT1/../PAT2/ 语法的更多信息:

    范围运算符

    ...在标量上下文中,“..”返回一个布尔值。运营商是 双稳态,像触发器一样,模拟行范围(逗号) sed、awk 和各种编辑器的运算符。

    • 对于-n 选项,请参见perldoc perlrun,它使Perl 的行为类似于sed -n

    • Perl Cookbook, 6.8 详细讨论了提取一系列行。

    【讨论】:

      【解决方案6】:

      你可以用sed 做你想做的事,方法是-n 抑制模式空间的正常打印。例如包含结果中的模式,你可以这样做:

      $ sed -n '/PAT1/,/PAT2/p' filename
      PAT1
      3    - first block
      4
      PAT2
      PAT1
      7    - second block
      PAT2
      PAT1
      10    - third block
      

      排除模式并打印它们之间的内容:

      $ sed -n '/PAT1/,/PAT2/{/PAT1/{n};/PAT2/{d};p}' filename
      3    - first block
      4
      7    - second block
      10    - third block
      

      分解为

      • sed -n '/PAT1/,/PAT2/ - 定位PAT1PAT2之间的范围并禁止打印;

      • /PAT1/{n}; - 如果匹配 PAT1 移动到 n(下一个)行;

      • /PAT2/{d}; - 如果匹配PAT2 删除行;

      • p - 打印所有位于/PAT1/,/PAT2/ 内且未被跳过或删除的行。

      【讨论】:

      • 感谢有趣的单行代码及其故障!我不得不承认我还是更喜欢 awk,它对我来说看起来更清晰 :)
      • 我完成了这个排序,却发现 hek2mgl 有一个更短的方法——看看他的 classic sed 解决方案。
      【解决方案7】:

      或者:

      sed '/START/,/END/!d;//d'
      

      这会删除除 START 和 END 之间的所有行,然后 //d 删除 START 和 END 行,因为 // 导致 sed 使用以前的模式。

      【讨论】:

        【解决方案8】:

        这就像上面 2 个热门答案(awk 和 sed)的脚注。我需要在大量文件上运行它,因此性能很重要。我将 2 个答案放到了 10000 次的负载测试中:

        sedTester.sh

        for i in `seq 10000`;do sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' patternTester >> sedTesterOutput; done
        

        awkTester.sh

         for i in `seq 10000`;do awk '/PAT1/{flag=1; next} /PAT2/{flag=0} flag' patternTester >> awkTesterOutput; done
        

        结果如下:

        zsh sedTester.sh  11.89s user 39.63s system 81% cpu 1:02.96 total
        zsh awkTester.sh  38.73s user 60.64s system 79% cpu 2:04.83 total
        

        sed 解决方案的速度似乎是 awk 解决方案 (Mac OS) 的两倍。

        【讨论】:

          【解决方案9】:

          这可能适用于您 (GNU sed),但前提是 PAT1PAT2 位于不同的行:

          sed -n '/PAT1/{:a;N;/PAT2/!ba;p}' file
          

          使用 -n 选项关闭隐式打印并像 grep 一样操作。

          注意所有使用范围成语的解决方案,即/PAT1/,/PAT2/ command,都会遇到相同的边缘情况,其中PAT1 存在但PAT2 不存在,因此将从PAT1 打印到文件末尾。

          为了完整性:

          # PAT1 to PAT2 without PAT1
          sed -n '/PAT1/{:a;N;/PAT2/!ba;s/^[^\n]*\n//p}' file 
          
          # PAT1 to PAT2 without PAT2
          sed -n '/PAT1/{:a;N;/PAT2/!ba;s/\n[^\n]*$//p}' file 
          
          # PAT1 to PAT2 without PAT1 and PAT2   
          sed -n '/PAT1/{:a;N;/PAT2/!ba;/\n.*\n/!d;s/^[^\n]*\n\|\n[^\n]*$/gp}' file
          

          注意在最后一个解决方案中,PAT1PAT2 可能在连续的行上,因此可能会出现进一步的边缘情况。 IMO 都被删除并且没有打印任何内容。

          【讨论】: