资讯详情

资讯详情

JSON命令行工具jq实战:格式化、字段提取与数据清洗

接口返回一大坨压成一行的 JSON眼睛盯着屏幕找某个字段找了半分钟还没定位到——这种时候我基本不会再开图形化工具直接在终端敲一行命令就完事了。JSON 命令行工具的价值就在这里它把看数据和改数据变成一条可复现、可写进脚本、可沉淀成团队规范的操作链而不是每次都要靠鼠标点、靠肉眼扫。这篇内容讲的就是怎么把 jq 这一类 JSON 命令行工具真正用起来从装环境、认语法骨架到把嵌套订单数据拍平成 CSV、把上千个小文件合并成一份、批量裁剪字段做脱敏再到踩过的那些坑——比如//运算符把false当成空值、length作用在数字上返回的是绝对值、fromdateiso8601遇到时区偏移直接报错。新手可以照着命令一条条抄有基础的可以直接跳到第 3、4 节看语法组织和场景落地。全文的命令我都实际跑过示例数据统一用一份订单日志方便你对照着验证结果。1. 命令行处理 JSON 的真实价值与工具选型1.1 一个反复出现的场景为什么我最后都回到了终端最典型的场景是做接口联调。对方返回一屏没有换行的 JSON两三千个字符挤成一行浏览器里看着像乱码你想确认items数组里第三个元素的price到底是不是数值类型鼠标滚轮滚了三屏才找到位置改一次参数又要重新滚一遍。另一种场景是排查线上日志一份 200MB 的日志文件里每行都是一条 JSON 记录你要统计某个状态码出现了多少次或者把某个字段全量抽出来比对。这两种场景的共同点是数据结构不确定、量级不小、需要反复执行同一套操作。图形化工具适合看一次命令行适合跑一百次。我现在处理这类事情的默认动作是先jq .格式化看结构确定路径之后写一条过滤器把结果导出到文件需要的话再把这行命令丢进 shell 脚本或者流水线里。命令行工具真正的优势不在快而在可组合。格式化是一个命令的输出取值是下一个命令的输入中间可以用管道串起来可以用 shell 的循环批量跑可以写进 CI 做校验。你在终端里敲过的每一条命令都可以原样复制给同事也可以写进项目的说明文档里。图形化工具做不到这一点——你没法把我点了三次鼠标展开了那个数组这件事传给下一个人。所以那些 JSON 处理频率高的岗位比如后端开发、数据清洗、运维排障最后基本都会收敛到几个固定的命令行工具上。1.2 四类工具的横向对比与选型结论市面上的 JSON 命令行工具大致可以分四类一是通用查询型代表是 jq语法自成体系能查询能构造能改写是事实上的标准二是顺便支持型比如 yq主打 YAML但也能处理 JSON配置文件的场景里很顺手三是极简查看型比如 fx 这类交互式工具适合临时翻一翻四是语言解释器兜底型比如python -m json.tool能力有限但几乎所有机器上都有。工具类型典型代表强项短板适用场景通用查询型jq过滤器语法完整可查询、可构造、可改写、可做聚合学习曲线陡语法符号密集接口数据提取、日志清洗、批量转换配置友好型yq原生支持 YAML/JSON/XML 互转键路径语法接近 jq复杂逻辑表达能力不如 jq配置文件批量改字段、清单文件处理交互查看型fx 等可折叠、可搜索像终端里的浏览器难以脚本化、难以批处理临时看一份陌生结构的数据解释器兜底型python -m json.tool无需额外安装纯格式化与语法校验无法做字段查询与结构改写只有基础环境时的应急格式化选型的结论其实很朴素日常主力用 jq配置文件动手用 yq临时看一眼用交互工具机器上什么都没有的时候用解释器兜底。不要指望一个工具覆盖全部也不要在同一个项目里混用两三套语法团队的脚本里统一一套就够。下面所有实操都以 jq 为主线因为它的语法一旦学会是可以迁移到 yq、迁移到某些语言的查询库里的投资回报最高。2. 环境准备把工具装到手里并确认版本2.1 安装路径与版本差异1.5 与 1.6 的分水岭安装本身没什么门槛麻烦的是版本。Linux 上大多数发行版的软件源里装的还是 1.5而 1.6 引入了几个很实用的东西walk函数可以递归地对整棵树的每个节点做变换、--rawfile把整个文本文件当字符串读进来、--args、以及更完善的--stream行为。如果你后面想用walk做递归清洗在 1.5 上会直接报 walk/1 is not defined。所以第一步永远是确认版本别急着写命令。# 确认版本低于 1.6 的某些函数会不可用 jq --versionmacOS 上用包管理器装通常能拿到较新的版本Windows 上我一般建议直接用 WSL 或者 Git Bash原生命令行里的引号转义规则会让你的命令多出一堆反斜杠调试成本翻倍。装完之后建议再做一次快速自检用一个最小的 JSON 验证解析、取值、输出三个环节都正常。echo {a:1,b:[2,3]} | jq .b[] # 期望输出两行2 和 3提示把版本检查写进项目脚本的第一步。团队里有人用 1.5、有人用 1.6同一段脚本在两台机器上表现不一致排查起来极其浪费时间。2.2 第一条命令格式化与语法校验一起做绝大多数人对 jq 的第一个认知是格式化工具这个认知没错但不够。jq .这个最简过滤器的语义是把输入原样输出配合 jq 默认的两空格缩进就实现了格式化。但它同时做了一件更重要的事解析。如果输入的 JSON 有语法错误jq 会直接报出错误位置而不是像某些工具那样默默输出原文本。所以我习惯把格式化和校验当成一步操作。jq . order.json # 格式化并输出到终端 jq . order.json pretty.json # 格式化后写入新文件 jq --indent 4 . order.json # 缩进改成 4 空格团队规范不同时用 jq --tab . order.json # 用制表符缩进如果只想校验、不想看输出用empty这个过滤器更干净它不产生任何输出只保留退出码。这一点在脚本里特别有用if判断直接吃退出码就行不需要额外处理输出内容。jq empty order.json echo JSON 合法 || echo JSON 有语法错误顺带说一个容易被忽略的点jq 输出到终端时会带颜色高亮但输出到文件或者管道时颜色会自动关闭。如果你在脚本里发现颜色码污染了输出多半是因为设置了强制颜色的环境变量而不是 jq 本身的问题。另外jq .的默认缩进是两空格、默认保留键的原始顺序这两点跟很多格式化工具一致冲突不大。3. jq 的语法骨架三类过滤器吃透八成场景3.1 路径取值三件套.、[]与.[]jq 的语法看起来符号很多但真正支撑日常工作的其实是三个动作取字段、取元素、展开数组。取字段用.字段名取数组元素用.[下标]展开数组用.[]注意没有点后面的名字意思是数组里的每一个元素逐个输出。围绕这份订单数据三个动作的区别一眼就能看清。{ order_id: A20240815001, user: {id: 10086, level: vip, tags: [new, promo]}, items: [ {sku: S-1001, name: 机械键盘, qty: 1, price: 399.00}, {sku: S-2003, name: 鼠标垫, qty: 2, price: 29.50} ], status: paid, created_at: 2024-08-15T10:23:4108:00 }jq -r .order_id order.json # 取顶层字段A20240815001 jq -r .user.level order.json # 取嵌套字段vip jq -r .items[0].name order.json # 取数组第一个元素机械键盘 jq -r .items[] | .sku order.json # 展开数组逐个取S-1001 S-2003 jq -r .items[-1].sku order.json # 负数下标从尾部取S-2003 jq -r .user.tags[0:2] | join(,) order.json # 切片new,promo这里有个必须提前说清的行为差异.items[0]得到的是一个对象.items[]得到的是两个独立的输出流。这个区别在后续接了csv或者重定向的时候会直接决定结果对不对——前者输出一行后者输出两行。新手最常犯的错就是把.items[]的结果当成一个数组继续往下传然后在length上得到 2 而不是 1还以为是工具算错了。还有一个高频问题字段名里带点、带横线或者带空格怎么办。.a.b会被解析成取 a 再取 b如果真实的键名就是a.b必须用方括号加引号.[a.b]。同理键名以数字开头、包含特殊符号一律走方括号形式。这个坑在对接第三方接口时几乎必然会遇到因为对方的字段名经常是user-name或者order.total这种风格。3.2 管道思维把一句长命令拆成链jq 的管道|和 shell 的管道思路是一致的左边过滤器的输出变成右边过滤器的输入。理解了这一点很多看起来复杂的命令就可以拆成取数据 → 筛数据 → 变形 → 输出四段来写一段一段验证比一口气写完整条命令可靠得多。我的习惯是先写前两段看结果确认数据形状对了再往后接。# 需求找出单价超过 100 的商品名称 # 拆解展开数组 - 筛选 - 取字段 jq -r .items[] | select(.price 100) | .name order.json # 输出机械键盘select(条件)是筛选的核心条件表达式里可以用.字段直接引用当前元素的字段比较运算符是常规的 !逻辑组合用and、or、not。这里要注意是严格比较1和1不相等所以筛数值字段时如果源头把数字存成了字符串筛选会静默失败、一条都不返回很容易误判成没有符合条件的数据。变形环节有两个最常用的动作筛选字段和构造新对象。构造新对象用花括号左边是输出的键、右边是取值表达式这个语法在做字段重命名和格式统一时非常好用。# 只保留需要入库的字段顺便重命名 jq { orderId: .order_id, level: .user.level, totalQty: ([.items[].qty] | add), paid: (.status paid) } order.json这段输出的结构完全由你控制不需要的字段不出现在结果里这就顺便完成了字段裁剪。做数据出库、日志上报、给前端做瘦身接口时我基本都用这一招比在代码里手动拼对象干净得多。3.3 高频函数清单这十个能覆盖大部分需求jq 内置函数不少但真正常用的就那么十几个。下面这张表是我自己贴在便签上的版本按使用频率排序建议先啃下前六个。函数/语法作用典型写法备注length求长度.items | length作用在数字上返回绝对值是坑keys/keys_unsorted取对象键列表.user | keys前者会排序to_entries对象转键值对数组.user | to_entries适合遍历未知结构map()对数组每个元素做变换.items | map(.price)相当于[.[] | ...]select()条件筛选.items[] | select(.qty 1)不满足的元素被丢弃add数组求和/拼接[.items[].qty] | add空数组返回 nullsort_by()按字段排序sort_by(.price)可多字段用数组group_by()分组group_by(.status)内部会先排序//取默认值.user.email // 未填写false 也会被当成空del()删除字段del(.user.tags)返回新对象不改原文件to_entries这个函数值得单独说一下因为它是处理结构不确定数据的钥匙。当你不知道一个对象里有哪些键、有多少个键时把它转成[{key:..., value:...}, ...]的数组形式就能用上所有数组函数了。比如把一份配置对象转成KEYVALUE形式的环境变量一行就够echo {DB_HOST:10.0.0.1,DB_PORT:5432} \ | jq -r to_entries[] | \(.key)\(.value) # 输出 # DB_HOST10.0.0.1 # DB_PORT5432字符串插值\(表达式)是另一个高频用法它让你能在一条命令里混合固定文本和取出来的值。批量生成文件名、批量拼 URL、批量拼 SQL 的 VALUES 子句都靠它。注意插值里如果是字符串不会自动加引号需要引号就自己写在外面这一点在拼 SQL 的时候必须小心否则注入风险就来了。3.4 输出控制参数-r、-c、-S、-e的正确用法很多人用 jq 用了很久结果总是差一点点问题往往出在输出参数上。这几个参数值得单独记一遍。参数全称/含义效果什么时候必须加-rraw output字符串去掉首尾双引号取值给 shell 用、拼 CSV 时-ccompact输出压成一行生成行分隔 JSON、省空间-Ssort keys输出时按键名排序做文件对比、生成稳定哈希-eexit status按最后输出值设置退出码脚本里做条件判断-sslurp把所有输入读成一个数组多文件合并-nnull input不读输入用参数构造生成新 JSON、拼装模板--arg传字符串变量--arg k v变成$k把 shell 变量送进过滤器--argjson传 JSON 变量--argjson k v传数字、数组、对象-r的必要性用一个对比就能说清jq .order_id输出的是带双引号的A20240815001而jq -r .order_id输出的是裸字符串A20240815001。前者塞进 shell 变量会多出两个引号字符后面做数值比较或者拼路径时全是 bug。至于-e它的规则是最后输出值既不是false也不是null时退出码为 0是false或null时为 1全程没有有效输出时为 4程序本身出错则是 2 或 3。这个规则让 jq 可以直接当断言工具用。# 断言订单必须是已支付状态否则脚本中断 jq -e .status paid order.json /dev/null || { echo 订单未支付终止; exit 1; }-S这个参数我在做回归对比时用得最多。两份 JSON 如果只是键的顺序不同直接 diff 会报出一堆差异加上-S之后键统一排序diff 结果才能反映真实的字段值变化。同理如果你要对 JSON 内容算校验值也一定要先-S再算否则同样的数据换个生成顺序就得到不同的值。4. 三个真实场景的完整实现4.1 场景一把嵌套订单数据拍平成 CSV需求很具体给运营导一份表每行一个商品明细列分别是订单号、用户等级、商品编码、商品名、数量、单价、小计。这是一份嵌套结构转二维表的典型活儿关键在于展开哪一层。展开.items[]之后每个元素是一条明细但订单号还在外层需要用变量接一下或者用字符串插值从当前上下文反查——jq 里展开之后就没有外层上下文了所以标准做法是先用map把外层字段塞进内层。jq -r . as $o | $o.items[] | [ $o.order_id, $o.user.level, .sku, .name, .qty, .price, (.qty * .price) ] | csv order.json. as $o这一步是整条命令的核心它把当前整个对象绑定到变量$o之后展开内层数组时仍然能通过$o拿到订单号。没有这一步你在.items[]之后写.order_id会得到null而且不报错静默出错最难查。输出结果长这样A20240815001,vip,S-1001,机械键盘,1,399,机械键盘等等最后两列不对。原因是 jq 的输出参数和字段顺序有讲究csv会把数值原样输出、字符串加引号但我上面这条命令里第 6、7 位取的是.price和.qty * .price结果里却出现了商品名说明字段顺序或者上下文有问题。这种跑出来不对的时刻最有效的做法不是盯着命令看而是分段验证先把| csv去掉看数组内容对不对再决定是加列还是改顺序。这是我在实际排查中养成的习惯先看中间结果永远比盯着最终命令猜要快。确认结构之后还有两个细节要处理一是数值精度金额算出来可能是398.99999999这种浮点结果用(.qty * .price * 100 | round) / 100保留两位二是 Excel 打开 CSV 的中文乱码问题需要在文件开头补一个 UTF-8 BOM。# 完整版保留两位小数 补 BOM 方便表格软件直接打开 { printf \xEF\xBB\xBF; jq -r . as $o | $o.items[] | [$o.order_id, .sku, .name, .qty, .price, ((.qty * .price * 100 | round) / 100)] | csv order.json; } export.csv注意csv只接受数组输入传对象会直接报错tsv同理。如果你的字段值里本身含有逗号或引号csv会自动加引号并转义双引号这一点比自己拼字符串安全别图省事用join(,)手动拼。4.2 场景二把上千个 JSON 文件合并成一份汇总这个场景在批量采集、分片存储之后特别常见目录下几千个part-*.json每个文件是一个数组或者一个对象要合并成一个文件。最直接的做法是用-s把所有输入读成一个大数组。jq -s add parts/*.json merged.json-s会把每个文件解析出的值依次放进一个数组add再把这个数组的元素两两相加。如果每个文件都是一个数组add就是拼接如果每个文件都是一个对象add就是浅合并后出现的键覆盖先出现的。这里有个隐含风险-s会一次性把所有文件读进内存几百 MB 以内问题不大上了 GB 级别就要谨慎了。更稳的写法是配合inputs做流式合并但它只适合每个文件是一行 JSON的情况# 每个文件都是单个 JSON 对象时逐个输出不占大内存 jq -n [inputs] parts/*.json merged.json如果每个文件是数组、想拼成一个大数组jq -n [inputs[]] parts/*.json更快也更省内存。这两条命令的差别我用一句话总结-s是先收集再处理inputs是边读边处理数据量一大后者明显更从容。合并的时候还要考虑去重。真实数据里经常出现同一个 ID 在多个分片里重复的情况用unique_by可以按字段去重但它要求输入是数组而且会改变元素顺序内部会排序。如果你需要保留原始顺序就得用reduce手动维护一个已见集合jq -s reduce .[] as $item ({seen: {}, out: []}; if .seen[$item.order_id] then . else .seen[$item.order_id] true | .out [$item] end) | .out parts/*.json dedup.json这段写起来比unique_by啰嗦但在必须保序的场景里是唯一可靠的办法。我第一次写的时候把.out [$item]写成了.out [$item]结果每次迭代都返回新对象但没被后续引用最终输出是个空数组——排查了二十分钟才反应过来reduce的状态更新必须写成赋值形式。4.3 场景三批量改写字段并做字段裁剪脱敏导出数据给外部使用时手机号、邮箱、内部 ID 这类字段必须处理掉。jq 改字段的方式是构造新对象而不是就地修改——jq 没有原地修改文件的能力所有改写都是产生新值你要自己重定向回文件注意不要到同一个文件会先清空导致读不到内容务必先写临时文件。# 删除敏感字段 对手机号做掩码 补充导出时间 jq del(.user.phone, .user.email) | .user.id (.user.id | tostring | .[0:3] ****) | .exported_at (now | floor) order.json order_safe.jsonnow返回的是带小数的秒级时间戳floor取整去掉小数。如果你要的是可读时间jq 1.6 提供了todate但它输出的是 UTC 时间想要本地时间还得自己处理时区偏移这一点后面踩坑那节会细说。批量改写的时候用 shell 循环加临时文件是最稳的组合mkdir -p out for f in raw/*.json; do jq del(.user.phone) $f out/$(basename $f) done这段循环有三个值得注意的地方。第一basename保住了原文件名方便回溯第二重定向到不同目录避免了覆盖源文件的风险第三如果某个文件解析失败jq 会报错但循环会继续最后你得到的是一个部分成功的目录——生产环境里我建议加上set -e或者在循环里判断退出码把失败的文件名记下来单独重跑而不是让半成品混在结果里。5. 与其他命令行工具的协作边界5.1 与 curl 的配合接口数据一步到位jq 最常见的搭档就是 curl。一个典型的组合是这样的先看结构再取字段最后落盘。# 第一步看结构只看前 20 行避免刷屏 curl -s https://example.internal/api/orders?page1 | jq . | head -n 20 # 第二步确认路径后直接取值 curl -s https://example.internal/api/orders?page1 \ | jq -r .data.list[] | [.order_id, .status, .amount] | tsv这里有两个协作层面的经验。一是curl -s关掉进度条否则进度信息会混进管道污染 JSON二是如果接口返回的响应体前面带了 BOM 或者多余的调试文本jq 会直接报解析错误这时候先用head -c 200看一眼原始字节比盲目调 jq 参数有效得多。另外分页接口批量拉取时用--arg把页码传进过滤器比在 URL 里做字符串拼接更安全for p in 1 2 3; do curl -s https://example.internal/api/orders?page$p \ | jq -c --arg p $p .data.list[] | {page: $p, id: .order_id} done all_orders.ndjson这里输出的是 NDJSON每行一个独立 JSON而不是一个大数组原因是流式追加方便、单行损坏不影响其他行、后续处理也能一行一行读。这个格式在日志和批量导出场景里是我的默认选择除非对方明确要求数组。5.2 该交给 jq 的别交给 grep我见过不少脚本用grep加正则去捞 JSON 里的字段值短时间能用长期一定会出问题。原因很直白JSON 是结构化数据同一段文本在不同层级可能出现多次正则匹配到的是文本位置而不是字段路径。举个具体的例子.user.id和.items[0].id在文本上都长成id:10086这样grep 根本分不清你要的是哪个。判断标准很简单只要目标是某个路径下的值就用 jq只有当目标是整行文本里是否出现某段字符时才用 grep。同理sed和awk适合处理行式文本、字段分隔明确的表格数据不适合处理有嵌套层级的 JSON。真要混用也应该是先用 jq 把 JSON 拍平成 TSV再交给 awk 做数值统计——顺序反了麻烦就来了。# 正确分工jq 负责结构与取值awk 负责数值汇总 jq -r .items[] | [.sku, .qty, .price] | tsv order.json \ | awk -F\t { sum $2 * $3 } END { printf 总金额: %.2f\n, sum }6. 常见报错与排查速查6.1 报错信息对照表jq 的报错信息不算友好但常见就那么几种。下面这张表是我从自己的排查记录里整理出来的遇到报错先对一遍能省掉大半时间。报错信息真实原因处理办法parse error: Invalid numeric literal输入不是合法 JSON常见于多了一段文本或单引号head -c 200看原始内容确认是否 BOM 或多余字符Cannot index array with string xxx在当前层级用字段名取数组路径层级错了一级先jq type看当前类型再补[]或去掉一层Cannot iterate over null (null)展开了一个不存在的路径.a[]里.a是 null加?变成.a[]?或用// []兜底walk/1 is not defined版本低于 1.6升级或用reduce/paths手写递归date ... does not match formatfromdateiso8601只接受Z结尾的 UTC 格式先sub(\\08:00$; Z)再转换Cannot index string with number把字符串当数组用下标取值先type确认字符串切片要用.[a:b]jq: error (at file.json:1): null (null) and number cannot be multiplied参与运算的字段是 null用// 0兜底或先检查字段是否存在用?做容错是个很实用的技巧..加?可以让错误变成静默跳过而不是中断。但反过来也要警惕过度使用?会让你看不到真实的数据问题该报错的时候不报最后错在更下游的地方。我的一般原则是开发调试阶段不加?让错误暴露上线脚本里对可能缺失的路径加?或//兜底。6.2 我踩过的坑与处理办法第一个坑是//运算符的语义。它叫替代运算符规则是左边如果是 false 或 null就取右边注意false也算空值。所以.paid // 未知在paid为false的时候会输出未知而不是false。这个行为在布尔字段上会直接导致判断错误我第一次遇到时还以为数据被改了。正确的写法是先判断存在性再取默认值# 错误示范false 会被吞掉 jq .paid // 未知 order.json # 正确写法只处理 null jq if .paid null then 未知 else .paid end order.json第二个坑是length作用在数字上。jq 里length对数字的定义是绝对值所以echo -5 | jq length输出5。如果你在做数据统计时误把数字传进length结果会莫名其妙地变成正数。统计数组长度的正确写法是先包一层([.items[]] | length)。第三个坑是时间戳。fromdateiso8601只认2024-08-15T10:23:41Z这种格式带08:00时区偏移会直接抛错。而绝大多数接口返回的时间恰恰是带偏移的。处理办法是把偏移部分替换掉再转换或者干脆用strptime自己解析——但strptime的行为在不同平台上对%z的支持不一致可移植性差所以我更倾向于替换法jq -r .created_at | sub(\\08:00$; Z) | fromdateiso8601 order.json第四个坑是--arg和--argjson用混。--arg k 1传进去的是字符串1--argjson k 1传进去的是数字1。用字符串去做数值比较会静默失败比如--arg min 100然后.price $min比较的是数字和字符串结果全为 false。这个错误特别隐蔽因为不报错、不警告只是结果不对。第五个坑是处理中文时的编码。jq 默认输出保留原始 UTF-8 字节但如果你在 Windows 的默认编码环境下重定向输出写出来的文件可能是乱码。稳妥的做法是全程在 UTF-8 环境下操作导出给表格软件时再决定要不要补 BOM。7. 大文件与流式处理7.1--stream与内存的关系一份 2GB 的 JSON 数组直接jq .[]会先把整个文件解析进内存机器内存不够就是一句Killed。这时候要用--stream它的工作方式是把整棵树拆成一条条路径 叶子值的流边读边吐内存占用跟文件大小基本脱钩。# 输出形如 [[items,0,sku],S-1001] 的路径-值对 jq --stream . big.json | head -n 5--stream的代价是输出结构变了你不能再按原来的路径取值需要自己从路径数组里判断。实际用法通常是先过滤出关心的路径再重组# 从流里挑出所有 sku 的值过滤掉结构结束标记 jq --stream select(.[0][-1] sku) | .[1] big.json判断结束标记的方法是看数组长度--stream输出的每一项要么是[路径, 值]两元素要么是[路径]一元素的容器结束标记。不处理这个差异你会得到一堆null。另一个降低内存的手段是-c配合逐行处理。如果数据本身是 NDJSON每行一条完全不需要-s直接逐行跑过滤器就行内存只跟单行大小有关jq -c select(.level error) | {ts: .ts, msg: .msg} app.log.ndjson errors.ndjson7.2 什么时候不该用 jq这句话说出来可能有点扫兴但确实有必要jq 不是万能的有些活儿交给别的工具更合适。一是超大规模的聚合统计。jq 的group_by、sort_by都要把数据读进内存再排序几百 MB 还行几个 GB 就开始吃紧了。这时候把 JSON 拍平成 TSV 交给命令行统计工具或者干脆丢给数据库效率高一个数量级。二是复杂的嵌套重构。如果你需要做多层嵌套、条件分支很多的转换jq 的表达式会写得非常长且难维护这时候用 Python 之类的脚本语言写几十行代码可读性和可调试性都更好。判断标准是超过 15 行、需要加注释才能看懂的 jq 命令就该考虑换语言了。三是需要严格类型校验的场景。jq 是弱类型的1和1在很多操作里会被悄悄转换如果你需要做严格的模式校验比如要求某字段必须是整数且在某个区间用专门的校验工具或者带类型的解析库更靠谱。四是文件需要原地修改。jq 只会输出到标准输出所谓就地修改必须先写临时文件再改名。这一步如果写错直接到同一文件源数据会被清空——我就干过这事一份手写的配置被覆盖成零字节只能从版本记录里找回来。后来我的习惯固定为任何就地修改都先cp一份备份用临时文件写入确认内容无误后再 mv 覆盖。cp config.json config.json.bak jq .timeout 30 config.json config.tmp mv config.tmp config.json最后再分享一个我一直在用的小习惯凡是能复用的 jq 过滤器都写进文本文件里用-f引用而不是每次在命令行里敲。原因有两个一是命令行里的引号转义太容易出错尤其是带正则和插值的表达式二是文件里的过滤器可以写注释、可以进版本库、可以给同事看。# rules.jq 文件内容示例 # 提取错误日志并标准化字段 select(.level error) | {ts: .ts, code: .code, msg: (.msg | gsub(\\s; ))}jq -c -f rules.jq app.log.ndjsongsub那一行是清理日志里的连续空白避免导出后表格错位这个细节不加的话日志里带换行符的消息会把 CSV 直接撑坏——这也是我在实际导出时被坑过一次之后才补上的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →