别再用 awk 硬拆 ps 和 df:用 jc-rs 把 Linux 命令输出变成可验证的 JSON
别再用 awk 硬拆 ps 和 df:用 jc-rs 把 Linux 命令输出变成可验证的 JSON
Linux 运维脚本常从一条看似无害的管道开始:ps、df、ss 的表格输出接上 awk、grep 和 cut,提取一列后再交给 CI、告警或资产盘点程序。它在当前机器、当前 locale、当前命令版本下通常能工作;但字段宽度、标题行、挂载路径里的空格或发行版差异一出现,面向人阅读的文本格式就成了自动化的隐性接口。
[jc-rs](https://github.com/OlegSotnikov/jc-rs) 选择了另一条路径:将命令输出和常见文本格式解析为 JSON。它是 Rust 实现的单一静态二进制,目标不是另造一套字段规范,而是兼容 Python 工具 [jc](https://github.com/kellyjonbrazil/jc) 已定义的解析结果。项目的 Cargo.toml 和仓库 LICENSE 都声明为 MIT;截至本文核查时,最新发布是 2026 年 8 月 7 日的 v0.4.0。
这篇文章不把它当作“用 JSON 替换一切 shell”的理由,而是讨论一个更实际的问题:当命令输出需要稳定地流入脚本、日志管道和持续检查时,怎样把解析层做成可验证的依赖。
先把人类表格变成程序可消费的数据
jc-rs 从标准输入读取文本,并用显式 parser 参数决定如何解析。可用 cargo binstall jc-rs 安装预构建二进制,或使用 cargo install jc-rs 从源码安装;安装后可以先从三个熟悉的命令开始:
ps aux | jc-rs --ps | jq '.[0]'
df -h | jc-rs --df
dig example.com | jc-rs -p --dig
前两个例子把已有命令的输出送入 stdin;第三个例子使用 -p,由 jc-rs 执行后面的命令,再按指定 parser 转换。对于自动化脚本,推荐优先选择第一种显式管道形式:命令执行和解析分成两个步骤,权限、超时、stderr 和失败码更容易由外层脚本控制。-p 更适合交互式验证或受控环境;如果命令参数来自工单、网络请求或其他不可信输入,就不应把它们直接拼接到会执行命令的入口中。
例如,下面的管道不再依赖 ss 表格中“第几列是端口”的假设,而是让 jq 在 JSON 字段上操作:
ss -tlnp | jc-rs --ss | jq '[.[].local_port] | unique'
df -h | jc-rs --df | jq '.[] | {
filesystem,
size,
used,
available,
mounted_on
}'
这里的重点不是 JSON 比文本“高级”,而是把脆弱点从正则表达式移到了一个有版本、有测试的解析器上。不过它也没有消除所有边界:不同命令版本、不同平台或特殊输入仍可能产生差异。上线前应当保存自己真实的 df、ss、ps 样本,并断言下游实际依赖的字段和类型,而不是只验证命令能返回一段 JSON。
让结构化输出进入检查,而不是只进入报表
把结果转换成 JSON 后,最有价值的下一步通常是把“看一眼”变成可失败的断言。例如,磁盘巡检不必让人阅读一整张表,而可以在 CI 或定时任务中筛出缺少挂载点的记录;端口巡检也可以先取出端口集合,再与团队维护的允许列表比较。下面的示例刻意把策略文件留在外部:解析工具负责得到事实,jq 负责收缩和选择,是否告警仍由调用方决定。
df -h | jc-rs --df | jq -e '.[] | select(.available == "0")'
ss -tlnp | jc-rs --ss | jq -c '[.[].local_port] | unique | sort'
jq -e 在过滤器没有产生结果时会以非零状态退出,这适合被 shell、CI 或 systemd timer 捕获;但它不是通用的“磁盘满了”规则,因为不同 parser、不同命令版本的字段类型和单位需要先通过真实样本确认。更稳妥的流程是:把一份经过脱敏的生产样本纳入测试,锁定你依赖的字段;升级 jc-rs 或操作系统镜像时重新运行;只有断言和业务阈值都通过,才更新基线。解析层提供结构,策略层仍应由团队明确拥有。
实时日志要逐条交付,而不是等到 EOF
批量命令可以输出一个 JSON 数组,但持续日志没有自然的结束时刻。README 把这一区分为流式 parser:使用 -u 时,记录会随输入到达逐行刷新,输出采用适合管道处理的 NDJSON,而不是等待整个文件结束后再拼成数组。
tail -f access.log | jc-rs -u --clf-s | jq -c '{remote_host, request, status}'
ping example.com | jc-rs -u --ping-s
这类管道适合把传统 access log 接到自定义巡检、告警预处理或本地分析程序中。注意两个失败模式。第一,tail -f 重启或日志轮转会影响输入连续性,解析器不负责补读;第二,NDJSON 的消费端必须按“每行一条 JSON”处理,不能把它误当成单个数组。将 jq -c 放进示例,能让每条结果保持一行,也便于后续程序按行读取。
兼容性数字为什么值得拆开看
新解析器最容易出现的风险不是“完全无法运行”,而是字段名、空值、时间戳或边缘输入与既有生态不一致。jc-rs 的一个有价值之处是把兼容性主张写成可执行的差分测试,而非只列出支持命令的数量。
项目使用固定在 jc v1.25.7 的子模块 fixture。tests/differential/validate.py 会遍历 fixture,并先让上游 jc 充当 oracle:只有上游本身能精确复现的输入/期望输出对,才进入匹配率的分母。仓库给出的复跑入口如下:
git clone --recurse-submodules https://github.com/OlegSotnikov/jc-rs.git
cd jc-rs
make differential
README 记录的报告为:943 对 fixture、236 个已知 parser,其中 934 对 oracle-valid 样本匹配,mismatch 与 error 均为 0;另有 9 对被上游拒绝,149 个 unmapped、18 个 no_input 被单独报告。因而“934/934、100%”的准确含义是:在该固定版本、该组经上游确认有效的 fixture 上匹配,并不等价于“现实中所有命令、所有发行版和所有恶意输入都必然兼容”。
这也是值得借鉴的工程做法:先定义分母,保留上游不认可的样本,再让 CI 在匹配率下降时失败。项目还固定 TZ=PST8PDT,因为上游 fixture 中有依赖本地时区计算的 epoch 字段;忽略这一环境条件会让一批时间戳样本悄悄退出可比较集合。测试数据、时区和 oracle 版本都应与百分比一起写出,否则漂亮的覆盖率没有可审计性。
静态分发适合极简环境,但不要混淆容器边界
项目提供预构建二进制、Cargo、Homebrew、npm 的 WASM 包以及 Docker 镜像。若团队希望从 release 资产部署,应该在 release 页选择与架构相符的文件,并根据同页的校验文件验证下载,而不是把二进制直接塞进基础镜像。源码构建则可执行:
make build
make check
README 还提供了 scratch Docker 镜像的 stdin 用法:
ps aux | docker run --rm -i appmasterio/jc-rs --ps | jq '.[0]'
docker run --rm -i appmasterio/jc-rs --df < df.txt
这里有一个容易忽略的取舍:scratch 镜像刻意不含 shell、libc 或包管理器,因此不能期待它在容器内替你执行 df 之类的命令。应在宿主机或目标容器执行采集命令,再通过 stdin 将文本交给 jc-rs;若必须在同一运行环境执行命令,使用独立二进制并明确控制执行权限更合适。
项目 README 也公布了与 jc 的自测 benchmark,并报告冷启动和不同批量输入的耗时差异。它们可以帮助评估“频繁拉起子进程”的场景,但应标注为项目在 Python 3.12、Linux x86-64、jc 1.25.7 下的自测,而不是独立基准结论。对长时间运行、解析量很大的服务,更应在自己的机器、命令样本与安全策略下测量端到端效果。
适合替换哪一层
jc-rs 最适合替换的是“把稳定但面向人类的系统命令输出交给自动化”这一层:本地巡检把 df 结构化、配置检查提取监听端口、遗留日志进入 NDJSON 管道,都是清晰的落点。它不适合成为不加验证的万能解析器,更不能代替权限控制、日志采集可靠性或跨平台兼容测试。
实践上可以从一条最脆弱的 awk 管道开始:保留原命令输出,加入 jc-rs 与 jq 的 JSON 断言,在 CI 中用团队自己的样本做回归。若 parser 字段、异常输入与升级行为都已被确认,再逐步将它变成可复用的运维组件。这样获得的不是“少写几段正则”,而是一条能够说明、复跑并持续验证的数据转换链路。
还有一个部署层面的检查值得加入:把解析器升级与命令本身的升级分开验证。发行版更新 ps、df 或日志格式时,先保存一小组脱敏的真实输出,再在新旧 jc-rs 版本上运行相同的 jq 断言。字段不存在、类型从字符串变为数字、时间格式改变,都应该让流水线明确失败,而不是由下游脚本把空值默默写进监控系统。对依赖解析结果触发告警或自动化修复的场景,这个回归样本比“命令能跑通”更有价值。
相关链接
- [jc-rs GitHub 仓库与 README](https://github.com/OlegSotnikov/jc-rs)
- [jc-rs v0.4.0 发布页](https://github.com/OlegSotnikov/jc-rs/releases/tag/v0.4.0)
- [jc-rs 的差分测试报告](https://github.com/OlegSotnikov/jc-rs/blob/master/tests/differential/REPORT.md)
- [jc-rs MIT License](https://github.com/OlegSotnikov/jc-rs/blob/master/LICENSE)
- [上游 jc 项目](https://github.com/kellyjonbrazil/jc)