做好一件事
Unix 的哲学是使用每一个简洁又专业的工具来做好每一件事,然后将它们像流水线一样连接起来操作数据。这是一个很棒的主意,并且在过去的几十年里一直运作良好。这一理念在 1978 年 Bell System Technical Journal 前言中有所概述,描述了 UNIX 分时系统:
列出的第一项和第二项实际上已经被我们反复实践。但现在是时候通过进一步定义非交互式的标准输出格式将这一理念带入 21 世纪。
具体来说,如果您想获取 linux 系统上以太网接口之一的 IP 地址,命令大概是下面这样:
$ ifconfig ens33 | grep inet | awk '{print $2}' | cut -d/ -f1 | head -n 1
看起来并不怎么优雅。
直到 2013 年左右,命令行输出非结构化文本仍具有许多意义。Unix/linux 有许多文本解析工具,如sed
, awk
, grep
, tr
, cut
, rev
等,它们可以通过管道连接在一起以重新格式化所需的数据,然后再将其发送到下一个程序。当然,这也经常是个令人头痛的地方,网上很多关于如何解析某某程序输出的问题。对于普通 linux 管理员,手动解析非结构化(在某些情况下只能是人类可读的)数据使工作无比艰难。
但是在 2013 年,一种称为 JSON 的特定数据格式被标准化为ECMA-404,随后在 2017 年被标准化为RFC 8259和ISO/IEC 21778:2017。JSON 现在在 REST API 中无处不在,用于序列化从Web 应用程序之间的数据到STIX2规范中的妥协指标再到配置文件的所有内容。所有现代编程语言都有 JSON 解析库,甚至还有用于命令行的 JSON 解析工具,例如jq
. JSON 无处不在,它易于使用,而且是一种标准。
如果 JSON 在我出生的 1970 年代就已经存在,那么 Ken Thompson 和 Dennis Ritchie 很可能已经将其作为推荐的输出格式来帮助程序在管道中“做好一件事”。
为此,我认为 linux 及其所有支持 GNU 和非 GNU 的实用程序应该提供 JSON 输出选项。我们已经看到一些命令开始对 json 有所支持,例如 systemctl
以及 iproute2
程序,还有 ip 命令中您可以使用 -j
选项以 JSON 格式输出。问题是许多 linux 发行版不包含提供 JSON 输出的版本(例如,目前的 centos)。即便如此,并非所有函数都支持 JSON 输出,如下所示:
这是 ip addr
的 JSON 输出:
$ ip -j addr show dev ens33
[{
"addr_info": [{},{}]
},{
"ifindex": 2,
"ifname": "ens33",
"flags": ["BROADCAST","MULTICAST","UP","LOWER_UP"],
"mtu": 1500,
"qdisc": "fq_codel",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "00:0c:29:99:45:17",
"broadcast": "ff:ff:ff:ff:ff:ff",
"addr_info": [{
"family": "inet",
"local": "192.168.71.131",
"prefixlen": 24,
"broadcast": "192.168.71.255",
"scope": "global",
"dynamic": true,
"label": "ens33",
"valid_life_time": 1732,
"preferred_life_time": 1732
},{
"family": "inet6",
"local": "fe80::20c:29ff:fe99:4517",
"prefixlen": 64,
"scope": "link",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}]
}
]
而即使带有 -j
选项的 ip route
命令,也不会以 JSON 格式输出结果:
$ ip -j route
default via 192.168.71.2 dev ens33 proto dhcp src 192.168.71.131 metric 100
192.168.71.0/24 dev ens33 proto kernel scope link src 192.168.71.131
192.168.71.2 dev ens33 proto dhcp scope link src 192.168.71.131 metric 100
其他一些更现代的工具,例如 kubectl
和 aws-cli
提供了更一致的 JSON 输出选项,可以更轻松地解析和流水线化输出。但是有许多旧工具仍然输出几乎无法解析的文本,例如netstat
, lsblk
, ifconfig
, iptables
等。有趣的是 Windows PowerShell 中使用了结构化数据,这是一件好事,Linux社区可以借鉴。
我们如何前进
解决方案是让所有这些传统的 GNU 和非 GNU 命令行程序支持 JSON 输出选项。所有操作系统 API,如 /proc
和 /sys
文件系统都应该以 JSON 格式序列化它们的文件,或者在另外替代的API中输出 JSON 数据。
同时,我创建了一个名为 jc
(https://github.com/kellyjonbrazil/jc) 的工具,可以将数十个 GNU 和非 GNU 命令和配置文件的输出转换为 JSON。您不需要为这些通用实用程序和文件创建解析器,而是使用 jc
命令即可。jc
充当解析中心,只需编写一次即可供所有人使用。
在线体验 jc 示例: https://jc-web-demo.herokuapp.com/
作为 Ansible filter plugin 使用jc: https://blog.kellybrazil.com/2020/08/30/parsing-command-output-in-ansible-with-jc/
使用 JC 实践
在 GNU/linux 将 Unix 哲学带入 21 世纪之前,让我们看看 jc
如何能让您的生活更轻松。以上面获取以太网 IP 地址的示例为例:
$ ifconfig ens33 | grep inet | awk '{print $2}' | cut -d/ -f1 | head -n 1
192.168.71.138
以下是使用 jc
以及 CLI JSON 解析工具 jq
获取相同结果的命令:
$ ifconfig ens33 | jc --ifconfig | jq -r '.[].ipv4_addr'
192.168.71.138
或者
$ jc ifconfig ens33 | jq -r '.[].ipv4_addr'
192.168.71.138
另一个示例是列出系统上所有侦听的 TCP 端口:
$ netstat -tln | tr -s ' ' | cut -d ' ' -f 4 | rev | cut -d : -f 1 | rev | tail -n +3
25
22
只是为了获得一个简单的端口号列表,竟然需要这么长的命令,让我们来看看使用 jc
和 jq
如何完成相同的事情:
$ netstat -tln | jc --netstat | jq '.[].local_port_num'
25
22
或者
$ jc netstat -tln | jq '.[].local_port_num'
25
22
使用语义增强的结构化数据与笨拙地解析低级文本相比,哪个更直观自不必多言。此外, JSON 输出还可以供任何高级编程语言(如 Python 或 JavaScript)使用,而无需进行行解析。
目前 jc 支持以下命令解析:arp
, df
, dig
, env
, free
, /etc/fstab
, history
, /etc/hosts
, ifconfig
, iptables
, jobs
, ls
, lsblk
, lsmod
, lsof
, mount
, netstat
, ps
, route
, ss
, stat
, systemctl
, systemctl list-jobs
, systemctl
list-sockets
, systemctl list-unit-files
, uname -a
, uptime
以及 w
.
注意:支持的程序和文件类型在此查看 https://github.com/kellyjonbrazil/jc#parsers
如果您对 jc 当前不支持的命令或文件类型有任何建议,请将其添加到评论中,我会看看我是否能弄清楚如何解析和序列化它。如果你想贡献一个解析器,非常欢迎!
有了 jc
命令我们可以让 linux 世界变得更美好,直到操作 OS 和 GNU 工具在加入我们!
{测试窝原创译文,译者:lukeaxu}