如何在两个模式之间打印线,包括或不包括(在 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 【问题描述】:我有一个类似下面的文件,我想打印两个给定模式 PAT1
和 PAT2
之间的行。
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
取消设置 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 sedsed -n '/PAT1/,/PAT2//PAT1/!/PAT2/!p' FILE
任何 sed1sed -n '/PAT1/,/PAT2//PAT1/!/PAT2/!p;;' FILE
甚至(感谢Sundeep):
GNU sedsed -n '/PAT1/,/PAT2///!p' FILE
任何 sed
sed -n '/PAT1/,/PAT2///!p;' FILE
在 PAT1 和 PAT2 之间打印行 - 包括 PAT1 但不包括 PAT2
以下仅包括范围开始:
GNU sedsed -n '/PAT1/,/PAT2//PAT2/!p' FILE
任何 sed
sed -n '/PAT1/,/PAT2//PAT2/!p;' FILE
在 PAT1 和 PAT2 之间打印行 - 包括 PAT2 但不包括 PAT1
以下仅包括范围结束:
GNU sedsed -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
(?<=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/
- 定位PAT1
和PAT2
之间的范围并禁止打印;
/PAT1/n;
- 如果匹配 PAT1
移动到 n
(下一个)行;
/PAT2/d;
- 如果匹配PAT2
删除行;
p
- 打印所有位于/PAT1/,/PAT2/
内且未被跳过或删除的行。
【讨论】:
感谢有趣的单行代码及其故障!我不得不承认我还是更喜欢 awk,它对我来说看起来更清晰 :) 我完成了这个排序,却发现 hek2mgl 有一个更短的方法——看看他的 classicsed
解决方案。 【参考方案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),但前提是 PAT1
和 PAT2
位于不同的行:
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
注意在最后一个解决方案中,PAT1
和 PAT2
可能在连续的行上,因此可能会出现进一步的边缘情况。 IMO 都被删除并且没有打印任何内容。
【讨论】:
以上是关于如何在两个模式之间打印线,包括或不包括(在 sed、AWK 或 Perl 中)?的主要内容,如果未能解决你的问题,请参考以下文章
如何在两种模式之间打印行,包括或排他(在sed,AWK或Perl中)?
当两个图案之间至少有一条线时,通过sed / AWK打印两个图案之间的线条
如何选择可能使用 awk/sed 多次出现的两个标记模式之间的行