我有一个类似下面的文件,我想打印两个给定模式 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,但我很想知道所有可能的组合,包括或排除模式。
如何打印两个图案之间的所有线条?
打印 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/{flag=1} 在一行中找到文本 PAT1 时设置标志。
/PAT2/{flag=0} 在一行中找到文本 PAT2 时取消设置标志。
flag 是具有默认操作的模式,即打印 $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。此时,它会打印存储的内容并清空缓冲区。
经典的 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
任何 sed
1
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 版本。
sed -n '/PAT1/,/PAT2/{//!p}' file
...从 manual empty regular expression ‘//’ repeats the last regular expression match
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
将 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,即。点也能找到换行符
(.*?) 非贪婪查找
\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
这是另一种方法
包括两种模式(默认)
$ 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
为了完整起见,这是一个 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 详细讨论了提取一系列行。
或者:
sed '/START/,/END/!d;//d'
这将删除除 START 和 END 之间的所有行,然后 //d
删除 START 和 END 行,因为 //
导致 sed 使用以前的模式。
通过使用 -n
抑制模式空间的正常打印,您可以使用 sed
做您想做的事情。例如,include 结果中的模式,您可以执行以下操作:
$ 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
解决方案。
这就像上面 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)的两倍。
如果 PAT1
和 PAT2
在不同的行上,这可能对您(GNU sed)有用:
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 都被删除并且没有打印任何内容。
不定期副业成功案例分享
$ awk '/PAT1/,/PAT2/' | grep "XYZ"
?