ChatGPT解决这个技术问题 Extra ChatGPT

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

我有一个类似下面的文件,我想打印两个给定模式 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 中的评论量(和对他们的投票)使我认为通用帖子将对未来的读者有很好的帮助。
@fedorqui,我没有收到回复,所以我决定尝试改进这个问题,以便在 Google 上获得更好的排名,并澄清范围是什么。如果您对它不满意,请随时恢复。
@Alex 不确定我的评论应该在哪里回复,但无论如何感谢您的编辑!我觉得很好。感谢您抽出宝贵时间

4
4 revs

打印 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。此时,它会打印存储的内容并清空缓冲区。


一个有用的代码,我已将其打包并上传为 #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"
A
Alex Harvey

经典的 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 版本。


嘿,经典更短!
不确定其他版本,但使用 GNU sed,第一个可以简化为 sed -n '/PAT1/,/PAT2/{//!p}' file ...从 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) 来源。非常感谢!
J
James Brown

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

k
karakfa

这是另一种方法

包括两种模式(默认)

$ 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

A
Alex Harvey

为了完整起见,这是一个 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 详细讨论了提取一系列行。


D
Daedelus

或者:

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

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


D
David C. Rankin

通过使用 -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/ 且未被跳过或删除的所有行。


感谢有趣的单线及其故障!我不得不承认我仍然更喜欢 awk,它对我来说看起来更清晰 :)
我完成了这个排序,却发现 hek2mgl 有一个更短的方法——看看他的 classic sed 解决方案。
a
aalosious

这就像上面 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)的两倍。


a
anubhava

如果 PAT1PAT2 在不同的行上,这可能对您(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

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅