POSIX Shell中如何仅提取匹配行的直接前导行(排除匹配行与自动生成的分隔符)
POSIX Shell中如何仅提取匹配行的直接前导行(排除匹配行与自动生成的分隔符)
你遇到的这个问题确实挺典型的——grep -B1虽然能拿到匹配行的前一行,但会连带输出匹配行本身,还会在非连续匹配时自动插入--分隔符,要是直接过滤这些分隔符又可能误删真正需要的内容(比如示例里的--行)。
先明确你的需求:给定这样的输入文本:
a b pattern c pattern d e pattern a -- pattern z
当匹配pattern时,只输出每个匹配行的直接前导行,最终结果要像这样:
b c e --
为什么grep方案行不通
用grep -B1 pattern处理的话,输出会是这样:
b pattern c pattern -- e pattern -- -- pattern
问题主要有两点:
- 会包含匹配的
pattern行本身 - 自动插入的
--分隔符会干扰结果,就算用grep -v pattern过滤掉匹配行,剩下的内容里还是有多余的分隔符:
b c -- e -- --
要是再把--也过滤掉,又会误删真正需要的那行--,显然不是理想的解决办法。
用POSIX兼容的awk解决方案
这里可以用awk实现精准的逻辑:只记录上一行内容,当当前行匹配pattern时,就输出上一行。这样既不会带匹配行,也不会产生多余分隔符。
命令如下:
awk 'prev != "" && /pattern/ {print prev} {prev=$0}' input.txt
简单解释下逻辑:
- 用变量
prev保存上一行的内容 - 当当前行匹配
pattern,并且prev不为空(避免第一行就匹配的无效情况),就打印prev(也就是匹配行的前一行) - 每次循环结束后,把当前行内容赋值给
prev,供下一次循环使用
运行这个命令后,就能得到你想要的结果:
b c e --
用POSIX兼容的sed解决方案
如果你更习惯用sed,也可以写出符合POSIX标准的脚本:
sed -n '${x;/./p;q};x;/pattern/{x;p;x};x' input.txt
解释下这个sed脚本的逻辑:
-n表示默认不输出任何行${x;/./p;q}:处理最后一行时,交换保持空间和模式空间,如果保持空间有内容就打印,然后退出x;/pattern/{x;p;x}:先交换保持空间(存着上一行内容)和模式空间(当前行内容),判断当前行(此时在保持空间)是否匹配pattern,如果匹配就交换回来,打印模式空间里的上一行内容,再交换回去- 最后
x:把当前行放到保持空间,作为下一次循环的上一行内容
这个脚本是逐行处理的,不需要把整个文件读入内存,效率很高。
备注:内容来源于stack exchange,提问作者umläute
相关产品推荐
相关产品推荐

