如何在两个模式之间打印线,包括或不包括(在 sed、AWK 或 Perl 中)?

Posted

技术标签:

【中文标题】如何在两个模式之间打印线,包括或不包括(在 sed、AWK 或 Perl 中)?【英文标题】:How to print lines between two patterns, inclusive or exclusive (in sed, AWK or 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,但我很想知道所有可能的组合,包括或不包括模式。

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

【问题讨论】:

我正在尝试向How to select lines between two marker patterns which may occur multiple times with awk/sed 发布规范答案,以便涵盖所有案例。我关注 It's OK to Ask and Answer Your Own Questions 并将答案发布为社区 Wiki,因此请随时改进! @Cyrus 是的,谢谢!在继续发布此问题/答案之前,我还检查了此问题。这里的重点是为此提供一套工具,因为my other answer 中 cmets 的数量(以及对他们的投票)使我认为通用帖子对未来的读者会有很好的帮助。 另见thelinuxrain.com/articles/how-to-use-flags-in-awk @fedorqui,我没有收到回音,所以我决定尝试改进这个问题,以便在 Google 上获得更好的排名,并澄清范围是什么。如果您对它不满意,请随时恢复。 @Alex 不确定我的 cmets 应该在哪里回来,但无论如何感谢您的编辑!我觉得很好。感谢您抽出宝贵时间 【参考方案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 取消设置 flagflag 是具有默认操作的模式,即 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 都被删除并且没有打印任何内容。

【讨论】:

以上是关于如何在两个模式之间打印线,包括或不包括(在 sed、AWK 或 Perl 中)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在两种模式之间打印行,包括或排他(在sed,AWK或Perl中)?

当两个图案之间至少有一条线时,通过sed / AWK打印两个图案之间的线条

模式重复时如何删除两个模式之间的线(删除包括模式)

如何选择可能使用 awk/sed 多次出现的两个标记模式之间的行

使用awk或sed在页眉和页脚之间获取文本,但不包括页眉和页脚

Sed - 在两个字符串之间的匹配模式后插入带文本的行