PAT1
和 PAT2
之间的行。1
2
PAT1
3 - first block
4
PAT2
5
6
PAT1
7 - second block
PAT2
8
9
PAT1
10 - third block
我已经阅读了如何使用awk / sed选择在多次出现的两个标记模式之间的行,但我很想看到所有可能的组合,包括或排除该模式。
如何打印两个模式之间的所有行?
PAT1
和 PAT2
之间的行。1
2
PAT1
3 - first block
4
PAT2
5
6
PAT1
7 - second block
PAT2
8
9
PAT1
10 - third block
我已经阅读了如何使用awk / sed选择在多次出现的两个标记模式之间的行,但我很想看到所有可能的组合,包括或排除该模式。
如何打印两个模式之间的所有行?
$ 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/{flag=1}
在找到行中的文本PAT1
时设置标志flag
。/PAT2/{flag=0}
在找到行中的文本PAT2
时取消标志flag
。flag
是一个具有默认操作的模式,即 print $0
:如果flag
等于1,则打印该行。这样,它将打印从出现PAT1
的时间开始到下一个PAT2
出现的所有行。这还将打印从最后一个PAT1
匹配到文件结尾的所有行。$ 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
。
$ 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 上打印。
$ awk 'flag; /PAT1/{flag=1} /PAT2/{flag=0}' file
3 - first block
4
PAT2
7 - second block
PAT2
10 - third block
将 flag
放置在开头,会触发之前设置的操作,并因此打印出结束模式而不是开始模式。
这基于 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为止。在那一点上,它会打印存储的内容并清空缓冲区。
那么经典的 sed
解决方案呢?
sed -n '/PAT1/,/PAT2/p' FILE
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' FILE
甚至更好(感谢Sundeep):
GNU sedsed -n '/PAT1/,/PAT2/{//!p}' FILE
sed -n '/PAT1/,/PAT2/{//!p;}' FILE
以下仅包含范围开始:
GNU sedsed -n '/PAT1/,/PAT2/{/PAT2/!p}' FILE
sed -n '/PAT1/,/PAT2/{/PAT2/!p;}' FILE
以下仅包含范围结束:
GNU sedsed -n '/PAT1/,/PAT2/{/PAT1/!p}' FILE
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版本的一行命令,编辑了此答案。
sed -n '/PAT1/,/PAT2/{//!p}' file
...参考手册中的说明,空正则表达式“//”会重复上一个正则表达式匹配。 - Sundeep如果一个正则表达式为空(即没有指定模式),sed应该像最后一个命令中使用的最后一个RE一样行事(无论是作为地址还是替换命令的一部分)。
看起来这里唯一剩下的问题就是如何解释“最后一个RE”。BSD对此有所说明。请看这里(第23点):https://github.com/freebsd/freebsd/blob/master/usr.bin/sed/POSIX - hek2mgl使用具有PCRE功能(如果可用)的grep
来打印标记和标记之间的行:
$ 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,即点号也可以匹配换行符(.*?)
非贪婪查找\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
为了完整起见,这里提供一种Perl解决方案:
perl -ne '/PAT1/../PAT2/ and print' FILE
或者:
perl -ne 'print if /PAT1/../PAT2/' FILE
perl -ne '/PAT1/../PAT2/ and !/PAT1/ and !/PAT2/ and print' FILE
或者:
perl -ne 'if (/PAT1/../PAT2/) {print unless /PAT1/ or /PAT2/}' FILE
perl -ne '/PAT1/../PAT2/ and !/PAT1/ and print' FILE
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。
这里有另一种方法
包含两种模式(默认)
$ 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
或者:
sed '/START/,/END/!d;//d'
//d
会删除START和END行,因为//
会让sed使用上一个模式。这里是对上面两个答案(awk和sed)的补充说明。我需要在大量文件上运行它,因此性能非常重要。我将这两个答案进行了10000次的负载测试:
for i in `seq 10000`;do sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' patternTester >> sedTesterOutput; done
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
相比于awk解决方案,sed解决方案似乎快了一倍(Mac OS)。
-n
来 抑制模式空间的正常打印,并对 sed
进行任何您想要的操作。例如,要在结果中 包含 模式,可以执行以下操作:$ 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/
范围内且未被跳过或删除的行。
sed
解决方案。 - David C. RankinPAT1
和PAT2
在不同的行上:sed -n '/PAT1/{:a;N;/PAT2/!ba;p}' file
使用-n
选项关闭隐式打印并像grep一样操作。
注意:所有使用范围习惯用法,即/PAT1 /,/ PAT2 /命令
的解决方案都会遇到相同的边缘情况,其中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
可能在连续的行上,因此可能会出现进一步的边缘情况。在我看来,两者都被删除,什么也不打印。