如何用Bash的AWK/SED/Grep解析对讲机只读通信日志并提取指定字段?
问题背景
我需要解析对讲机的只读通信日志,日志每条记录行数不固定(从2-3行到超8行都有),每条记录有固定起始标识received voice header from和结束标识%。折腾SED代码11小时都没搞定,现在想编写Bash脚本通过tail -fn0实时监控日志,提取指定6个字段,附上了尝试的脚本但没成功,同时想了解AWK、SED、Grep的适用场景和学习路径。
日志样例
M: 2019-06-08 18:15:24.927 DMR Slot 2, received network voice header from KS3X to TG 91
M: 2019-06-08 18:15:25.402 DMR Talker Alias (Data Format 1, Received 6/20 char): 'KS3X D'
M: 2019-06-08 18:15:25.410 DMR Slot 2, Embedded Talker Alias Header
M: 2019-06-08 18:15:25.412 0000: 04 00 68 4B 53 33 58 20 44 ..hKS3XD
M: 2019-06-08 18:15:26.111 DMR Talker Alias (Data Format 1, Received 13/20 char): 'KS3X DMR ID: '
M: 2019-06-08 18:15:26.120 DMR Slot 2, Embedded Talker Alias Block 1
M: 2019-06-08 18:15:26.121 0000: 05 00 4D 52 20 49 44 3A 20 *..MR ID: *
M: 2019-06-08 18:15:26.824 DMR Talker Alias (Data Format 1, Received 20/20 char): 'KS3X DMR ID: 1142129'
M: 2019-06-08 18:15:26.824 DMR Slot 2, Embedded Talker Alias Block 2
M: 2019-06-08 18:15:26.824 0000: 06 00 31 31 34 32 31 32 39 ..1142129
M: 2019-06-08 18:16:15.921 DMR Slot 2, received network end of voice transmission, 51.2 seconds, 0% packet loss, BER: 0.0%
需要提取的6个字段
- 第一行
from与to间的呼号(示例中为KS3X) - 第一行
to到行尾的信道(示例中为TG 91) - 第8行末尾的DMR ID(示例中为
1142129) - 第11行的通话时长(示例中为
51.2 seconds) - 第11行的丢包率(示例中为
0%) - 第11行的BER百分比(示例中为
0.0%)
尝试的脚本
#!/bin/bash ACCESS_TOKEN="o.WOgpVaaEBjoVLGKS3VzFnsO4xGClTRiF" tail -fn0 /var/log/pi-star/MMDVM-2019-06-08.log | \ while read line ; do echo "$line" | gawk ' match($0, /received.*voice header from ([[:alnum:]]+) to ([[:alnum:]]+ [0-9]+)/, a) { in_record = 1 call_sign = a[1] channel = a[2] } in_record && match($0, /DMR ID: ([0-9]+)/, a) { dmr_id = a[1] } in_record && match($0, /([0-9.]+) seconds, ([0-9]+)% packet loss, BER: ([0-9.]+)%/, a) { in_record = 0 print call_sign, channel, dmr_id, a[1], a[2], a[3] } ' done
可行解决方案
你的脚本思路没问题,但核心问题是单行处理的局限性——用while read line逐行传给gawk时,每次gawk只处理一行,之前存储的变量(比如call_sign、dmr_id)会在每次调用gawk时重置,无法跨行保留状态。
修改后的脚本直接让gawk处理整个日志流,就能持续跟踪记录状态:
#!/bin/bash ACCESS_TOKEN="o.WOgpVaaEBjoVLGKS3VzFnsO4xGClTRiF" tail -fn0 /var/log/pi-star/MMDVM-2019-06-08.log | gawk ' # 匹配记录起始行,初始化状态并提取呼号和信道 match($0, /received network voice header from ([[:alnum:]]+) to (TG [0-9]+)/, a) { in_record = 1 call_sign = a[1] channel = a[2] dmr_id = "" # 重置DMR ID,避免污染下一条记录 } # 在记录内匹配完整的DMR ID行(防止中间行的部分内容干扰) in_record && match($0, /DMR ID: ([0-9]+)'\''$/, a) { dmr_id = a[1] } # 匹配记录结束行,提取时长、丢包率、BER并输出所有字段 in_record && match($0, /received network end of voice transmission, ([0-9.]+) seconds, ([0-9]+)% packet loss, BER: ([0-9.]+)%/, a) { in_record = 0 # 可选:确保所有字段都已提取,避免输出不完整记录 if (call_sign != "" && channel != "" && dmr_id != "") { print call_sign, channel, dmr_id, a[1], a[2], a[3] } } '
关键调整点
- 去掉
while read line+echo "$line" | gawk的组合,直接让gawk处理tail的输出流,保持变量跨行状态。 - 优化DMR ID匹配正则:
/DMR ID: ([0-9]+)'\''$/——匹配行尾的单引号,确保只提取完整的ID行(避免中间行'KS3X DMR ID: '的干扰)。 - 增加
dmr_id = ""重置逻辑,防止上一条记录的ID污染下一条。 - 可选的字段非空判断,避免输出不完整的记录。
AWK、SED、Grep适用场景梳理
Grep
- 最适合:快速搜索匹配行、简单文本过滤/提取。比如找所有包含
received voice header的行,或用-o提取特定匹配片段。 - 不适合:复杂多字段提取、跨行处理、逻辑判断。
SED
- 最适合:基于行的文本替换、简单行编辑(删除、插入、替换),适合批量修改文本内容。比如批量替换日志日期格式,或删除特定行。
- 不适合:复杂状态跟踪、多字段关联提取(比如你的跨行日志解析场景),SED正则处理能力较弱,状态管理麻烦。
AWK
- 最适合:结构化文本解析、多字段提取、跨行状态跟踪、逻辑判断、统计分析。像你的日志解析场景,需要跟踪一条记录的起始/中间/结束行,提取不同位置的字段,AWK的变量状态和模式匹配能力正好适配。
- 不适合:简单单行替换(SED更高效)、纯搜索(Grep更快)。
学习建议
- AWK:先掌握基础的模式-动作结构、变量使用、
match()等核心函数,再练习状态跟踪和多文件处理,从日志解析、CSV处理等实际场景入手。 - SED:重点学习基本命令(
s替换、d删除、a插入)、正则语法和地址范围使用。 - Grep:掌握常用选项(
-i忽略大小写、-o只输出匹配部分、-v反向匹配、-E扩展正则)。
内容的提问来源于stack exchange,提问作者Jae Nulton

