如何在新版grep中用正则匹配多行并提取最后一组统计数据?
问题描述
我有一个包含多组内存统计数据的文件,每组以含"TOTAL"的行开头和结尾,行数不固定。需要提取最后一组统计数据,原本使用正则(?m)^.*?TOTAL(?s).*?(?m)TOTAL.*?$及命令:
tac con.log | grep -Po "(?m)^.*?TOTAL(?s).*?(?m)TOTAL.*?$" -m1 | tac
在grep 2.5.3环境中能得到正确结果,但在Rocky Linux 9的grep 3.6里完全匹配不到。这个正则在测试工具里有效,推测是新版grep的特性变化导致的,想知道新版grep对这类正则有什么特殊要求,或者有没有其他适合bash脚本的实现方法。
文件内容示例:
2023-01-01 01:00:00 TOTAL MEMORY ALLOCATION CONSUMPTION: 2023-01-01 01:00:00 COMPONENT | USAGE (%) 2023-01-01 01:00:00 class.zzz.aaa.bbb | 32 2023-01-01 01:00:00 class.fff.aaa.ggg | 20 2023-01-01 01:00:00 TOTAL: 52% out of 100% allocated memory consumed 2023-01-01 01:00:00 TOTAL MEMORY ALLOCATION CONSUMPTION: 2023-01-02 01:00:00 COMPONENT | USAGE (%) 2023-01-02 01:00:00 class.xxx.aaa.bbb | 42 2023-01-02 01:00:00 class.bbb.aaa.zzz | 10 2023-01-02 01:00:00 class.zzz.xxx | 21 2023-01-02 01:00:00 class.xxx.sss.ggg | 5 2023-01-02 01:00:00 TOTAL: 78% out of 100% allocated memory consumed 2023-01-01 01:00:00 TOTAL MEMORY ALLOCATION CONSUMPTION: 2023-01-03 01:00:00 COMPONENT | USAGE (%) 2023-01-03 01:00:00 class.xxx.yyy.zzz | 10 2023-01-03 01:00:00 class.xxx.zzz.aaa | 20 2023-01-03 01:00:00 class.zzz.aaa.bbb | 30 2023-01-03 01:00:00 TOTAL: 60% out of 100% allocated memory consumed
新版grep不匹配的原因
grep 3.x版本改用了PCRE2库(旧版是PCRE),对正则的处理逻辑有不少调整,主要影响点:
- 内嵌修饰符的作用范围:新版中
(?m)和(?s)这类修饰符只对自身位置之后的正则生效,旧版可能是全局生效,原正则里的修饰符切换逻辑在新版中会导致匹配范围混乱。 - 多行/单行模式的严格性:PCRE2对
^/$在多行模式下的匹配、.在单行模式下的换行匹配规则更严格,原正则的模糊跨行匹配逻辑无法正确捕获内容。 - 输入换行处理:新版grep对
tac反向后的文本行首行尾匹配逻辑有变化,导致原本的行锚定失效。
解决方案
方法1:修正正则适配新版grep
简化正则,统一修饰符,避免冲突:
tac con.log | grep -Po '(?s)^.*?TOTAL.*?TOTAL.*?$' -m1 | tac
如果要避免中间含TOTAL的行干扰(比如日志里有其他带TOTAL的行),可以更精准匹配开头和结尾的特征:
tac con.log | grep -Po '(?s)^.*?^.*TOTAL MEMORY ALLOCATION CONSUMPTION:.*?^.*TOTAL:.*?$' -m1 | tac
核心是用(?s)开启单行模式让.匹配换行,直接捕获反向文本中第一个完整的TOTAL块,再转回来就是原文件的最后一组。
方法2:用awk实现(更稳定,不受grep版本影响)
awk处理多行块的逻辑更直观,适合这种按固定标记分割的文本:
awk '/TOTAL MEMORY ALLOCATION CONSUMPTION:/ {block=""} {block=block $0 "\n"} /TOTAL:.*allocated memory consumed/ {last_block=block} END {print last_block}' con.log
逻辑说明:
- 碰到每组的开头行时,清空临时存储块
block - 每一行都追加到
block里 - 碰到每组的结尾行时,把当前
block保存为last_block - 最后输出
last_block,就是最后一组数据
方法3:用sed实现
利用sed的地址范围匹配:
tac con.log | sed '/TOTAL:.*allocated memory consumed/,/TOTAL MEMORY ALLOCATION CONSUMPTION:/p' | tac | sed '/^$/d'
逻辑说明:
tac把文件内容反向- sed匹配从第一个结尾行到第一个开头行的内容(对应原文件的最后一组)
- 再用
tac转回来恢复顺序 - 最后用
sed去掉可能的空行
内容的提问来源于stack exchange,提问作者justabitjanky
相关产品推荐
相关产品推荐

