
大家好,我是情报小哥~
前几天跟一个嵌入式linux软件前辈,他跟我分享了一句话: "在资源匮乏的嵌入式世界里,最锋利的工具往往早已躺在 /bin 目录下。",这是踩了多少坑才有这么痛的领悟~
大家想象一下这样的场景,当你蹲在调试台前,盯着串口终端里疯狂滚动的日志,根本刹不住车。可能是驱动初始化失败,可能是某个进程反复被 OOM Killer 干掉,但你没办法装一个带高亮的日志分析器——这个板子只有 64MB 内存,跑着裁剪过的 BusyBox,连 less 都是简化版。
你现在急需把网络接口的 IP 改掉,可板子上没有 vim,甚至没有 nano。你总不能为了改一行配置,就把文件系统重新打包烧录一次吧。
这些场景,几乎每一位嵌入式工程师都经历过。我们习惯了资源的限制,也因此比任何人都更懂得克制地使用工具。今天想和你聊的,正是嵌入式调试中最克制、也最锋利的三件武器:grep、sed、awk。
Unix 世界有句老话:一切皆文件。日志是文件,配置是文件,进程信息是文件,就连 /proc 里的内存、中断、CPU 状态也都是文件。一旦接受了这个设定,你会发现调试问题的核心,就变成了在文件构成的文本流里寻找线索、裁剪信息、提取结论。
更重要的法则是:小工具通过管道组合。grep 负责过滤,sed 负责修改,awk 负责统计和格式化。它们每一个都只做一件小事,但用 | 串起来,就能在几秒钟内完成你原本想写一个 Python 脚本去搞定的事情。
这恰恰是嵌入式系统设计的精髓——做减法。不引入新的依赖,不动用编译器,就地取材,用系统已有的能力把问题解决掉。当你习惯了“组合而非重写”,你会发现自己的设计思路也开始变得清爽:模块化,接口清晰,可替换,不膨胀。可以说这是一项内功。
嵌入式开发最不缺的就是日志。内核启动、驱动加载、系统服务……它们一股脑涌向串口控制台或 /var/log/messages。你需要做的第一件事,就是把“噪音”过滤掉。
假设设备启动后网络异常,你想看看网卡有没有报错。不用打开一个完整的日志文件去翻,一条命令即可:
dmesg | grep -i "error\|fail\|warn"grep -i 忽略大小写,\| 用来匹配多个关键词。管道那一头是 dmesg 的输出,这一头直接筛出所有包含 error、fail、warn 的行。几秒钟之内,你可能就会看到:
[ 12.345678] fec 30be0000.ethernet: failed to get clock线索瞬间浮现。你还可以用正则进一步精确过滤,比如只想看内存分配失败相关的内核错误,ENOMEM → 系统调用中内存分配失败的错误码(例如 mmap、malloc 失败)。:
dmesg | grep -E "Out of memory|ENOMEM"这里的grep -E:使用扩展正则表达式(Extended regex)进行匹配,这就是 grep 给我们的核心能力:用极其低廉的代价,快速缩小问题域。在资源受限的板子上,你甚至不需要把日志保存成文件,串口输出一管,直接实时过滤,调试回路被压缩到最短。
嵌入式设备上修改配置是一个高频动作。网络参数、启动脚本、设备树覆盖项……但你的板子可能只有 BusyBox,没有你熟悉的编辑器。这时 sed 就是你的“流编辑器”,它不打开文件,却能瞬间完成修改。
比如你正通过串口调试网络,需要把 /etc/config/network 里的 IP 地址从 192.168.1.100 改为 192.168.2.100。不需要 vim,一行搞定:
sed -i 's/option ipaddr.*/option ipaddr 192.168.2.100/' /etc/config/network-i 表示直接修改文件(BusyBox 的 sed 同样支持),s/old/new/ 做替换。你甚至可以用它来批量调整启动脚本,比如把所有打印调试信息的地方注释掉:
sed -i '/echo "DEBUG/s/^/#/' /etc/init.d/my_app其中'/echo "DEBUG"/s/^/#/':一个完整的 sed 表达式,由“地址”和“命令”组成:
/echo "DEBUG"/:地址匹配条件。它会选中所有包含字符串 echo "DEBUG" 的行(注意双引号是字面匹配的一部分)。
s/^/#/:替换命令,将匹配行的行首(^)替换成 #,即在该行最前面插入注释符。
/etc/init.d/my_app:目标文件路径(通常是 System V init 风格的启动脚本)。
这种“无编辑器也能生产”的能力,在只有 initramfs 的紧急维护模式下尤其救命。它让你保持对系统的最小扰动——不需要挂载可写分区,不需要启动额外服务,只是让文本流经过一个规则,就完成了交付。
awk 就是一把可以现场编程的瑞士军刀。它尤其擅长处理格式化的文本,比如 /proc 下的各种接口。
举个例子,你想快速查看当前中断的分布,找出哪个中断源最繁忙。/proc/interrupts 是这个样子的:
CPU0 CPU1 29: 12345 0 GIC 29 arch_timer 37: 9876 1024 GIC 37 mmc0 44: 0 50000 GIC 44 eth0用 awk 统计每个中断的总触发次数,并找出最高者:
awk 'NR>1 {sum=$2+$3; if(sum>max) {max=sum; name=$NF}} END {print name, max}' /proc/interrupts解读一下:NR>1 跳过标题行,累加 CPU0 和 CPU1 的计数,通过比较找出最大值,最后打印中断名称和次数。执行结果可能就是 eth0 50000,直指问题核心。
再比如,你想在 shell 脚本里获取可用内存大小,而不用去写 C 解析 /proc/meminfo:
awk '/MemAvailable/ {print $2}' /proc/meminfo/MemAvailable/:匹配包含 MemAvailable 的行,{print $2}:打印该行的第二个字段, 这就是我常说的“逻辑前移”——把数据处理直接前置到管道里。你不需要把数据拉到 PC 上用 Excel 画图,也不需要写一个独立的分析脚本。在数据产生的源头,一条 awk 命令就能把结论提取出来,直接用于下一步判断。这种即时反馈,让调试动作变得非常紧凑。
单个工具已经足够锋利,但它们真正的威力,在组合时才会爆发。我曾经遇到一个案例:设备间歇性重启,日志滚动太快,串口记录也看不出规律。我用下面这条管道,在三十秒内锁定了嫌疑进程:
dmesg | grep -i "killed process" | sed 's/.*process //; s/(.*)//' | awk '{a[$1]++} END{for(i in a) print i, a[i]}' | sort -nrk2我们拆解一下这个“文本流水线”:
结果可能是一目了然的:
my_app 12log_server 3那个被杀了 12 次的 my_app,显然就是内存泄漏的元凶。没有任何图形界面,没有安装任何分析工具,仅仅用板子上自带的命令,你就完成了一次完整的故障定位。这正是 Unix 哲学在嵌入式世界的最佳实践:让一个个单纯的小工具,通过管道组成解决复杂问题的临时团队。
在嵌入式 Linux 里摸爬滚打越久,越会敬畏“简单”。我们手中的板子可能跑不动 Python,可能连 gdb 都装不全,但 /bin 下的 grep、sed、awk 几乎永远都在。它们是 BusyBox 的一部分,是内核构建后默认存在的元素,是你最可信赖的原语。
养成这样的习惯:每当你想分析一个问题,先别急着写代码。试着在串口终端里用管道快速验证想法,把中间结果打印出来看一看。当假设被迅速证实,再把那行命令固化成脚本,放进产品的诊断工具里。这种工作流——假设 → 管道验证 → 固化——会极大地缩短你的调试回路。
最后你会发现,grep、sed、awk 已经不单是工具了,它们变成了你思维的一部分。遇到问题,你的第一反应会是如何用文本流去描述它、过滤它、提炼它。在资源极度受限的嵌入式世界里,这种思维不再束缚你。
持续获取嵌入式实战干货,关注、标星 公众号不错过每一篇技术解析~
推荐好文点击蓝色字体即可跳转
☞专辑|Linux应用程序编程大全 ☞ 专辑|学点网络知识 ☞ 专辑|手撕C语言 ☞ 专辑|手撕C++语言
☞ 专辑|经验分享 ☞ 专辑|从单片机到Linux ☞ 专辑|电能控制技术 ☞ 专辑|嵌入式必备数学知识 ☞ MCU进阶专辑
☞ 经验分享