Crush 内置 jq 命令:零依赖的 JSON 处理技能与源码级实现解析

Crush 内置 jq 命令:零依赖的 JSON 处理技能与源码级实现解析 Crush 内置 jq 命令零依赖的 JSON 处理技能与源码级实现解析【免费下载链接】crushGlamourous agentic coding for all 项目地址: https://gitcode.com/gh_mirrors/crush3/crush导读Crush 在自身 shell 环境中内置了一个完整的jq命令基于纯 Go 的 gojq 实现无需安装任何外部二进制即可完成 JSON 的查询、过滤、重塑与构造。本文以 internal/skills/builtin/jq/SKILL.md 为骨架完整讲解其支持的命令行参数、与标准 jq 的行为差异、常用实战模式并结合 internal/shell/jq.go 的源码与 internal/shell/jq_test.go 的测试深入剖析其注册机制、上下文取消语义与超时处理原理。读完本文你将掌握在 Crush 中高效处理 API 响应、配置文件、日志输出等结构化数据的完整技能并能理解如何将 jq 用于 hook 脚本等自动化场景。一、为什么 Crush 要内置 jq在 Agent 工作流中模型需要频繁地对工具输出做结构化处理从 API 响应中提取字段、过滤数组、重塑对象、构造 JSON 参数。Crush 选择将jq直接内置而不是要求用户安装外部二进制带来了三个直接收益零外部依赖只要运行 Crushjq就可用无需apt install jq或 brew 安装行为可控内置实现与标准 jq 在细节上有明确差异见下文Agent 可以依赖这些稳定的行为可被中断内置实现与 Crush 的 hook 超时机制深度集成长查询可以被 context 取消这一设计在 internal/shell/jq.go 中通过ctx轮询实现详见第五节。从源码结构看这个技能文件位于internal/skills/builtin/jq/SKILL.md属于 Crush 的builtin skill体系——技能正文通过go:embed嵌入二进制启动时由 internal/skills/embed.go 的DiscoverBuiltinWithStates扫描发现并以其 frontmatter 中的name与description注入模型上下文见 internal/skills/skills.go 的ToPromptXML。也就是说本文讲解的这份 SKILL.md 本身就是 Crush 向模型传达「何时应使用 jq」这一能力的载体。二、内置 jq 的注册与可用范围jq并不是一个外部进程而是注册在 Crush shell 解释器内的builtin 命令。注册发生在 internal/shell/builtins_registry.gofunc init() { RegisterBuiltin(jq, handleJQ) }RegisterBuiltin将handleJQ定义于 internal/shell/jq.go注册进进程内的 builtin 表mvdan.cc/sh/v3解释器在执行命令时直接命中该处理器全程无进程启动开销。因此jq在bash 工具中开箱即用不依赖系统的jq二进制脚本迁移到没有 jq 的环境也能运行它同样可用于PreToolUse 等 hook 脚本中见第七节实战。输入来源支持两种标准输入流stdin和文件参数即jq .foo file.json的形式同样受支持。当未提供任何 filter 时默认 filter 为.原样输出输入这一点在 internal/shell/jq.go 的queryStr 分支中实现。三、支持的命令行参数Flags内置 jq 支持以下参数与标准 jq 的常用子集保持一致Flag说明-r,--raw-output字符串直接输出不加引号-j,--join-output同-r但输出末尾不加换行符-c,--compact-output单行紧凑 JSON 输出-s,--slurp将所有输入读入一个数组-n,--null-input以null作为输入忽略 stdin-e,--exit-status若最后输出为false或null退出码为 1-R,--raw-input将每一行作为字符串而非 JSON 读取--arg name value将$name绑定为字符串值--argjson name value将$name绑定为解析后的 JSON 值file 参数放在 filter 之后同样受支持jq .foo file.json。参数解析的源码细节结合 internal/shell/jq.go 的参数解析循环有几点值得注意-j是-r的超集解析--join-output时同时置位rawOutput差异仅体现在输出末尾是否追加换行由writeValue中的join分支决定。--argjson会校验 JSON 合法性--argjson name value中 value 必须能通过json.Unmarshal否则报错并以退出码 2 返回见 internal/shell/jq.go 第 101-113 行。--之后全部视为文件参数避免文件名以-开头时的歧义。未知选项filter 已解析后若再出现-开头的 token会报 unknown option 并以退出码 2 退出。退出码约定从 internal/shell/jq.go 可确认2参数错误或输入解析错误如--argjson非法 JSON3filter 编译错误gojq.Parse或gojq.Compile失败5filter 执行期错误迭代器返回 error1仅在-e模式下最后输出值为false或null0正常执行。四、与标准 jq 的差异内置 jq 使用 gojqgithub.com/itchyny/gojq纯 Go 实现与标准 jq 存在以下关键差异编写 filter 时需要特别注意对象键不做排序键默认按字典序排列keys_unsorted与-S参数不可用。任意精度整数大整数保留完整精度加法、减法、乘法、取模以及整除时的除法运算均不损失精度。字符串索引返回子串abcde[2]返回c字符串而非标准 jq 中的字符编码数字。不支持的参数/特性--ascii-output、--seq、--stream、--stream-errors、-f/--from-file、--slurpfile、--rawfile、--args、--jsonargs、input_line_number、$__loc__以及部分正则特性反向引用 backreferences、环视 look-around。YAMLgojq 本身支持--yaml-input/--yaml-output但 Crush 内置 jq 当前并未暴露这两个 flag。其中「任意精度整数」是 gojq 相对标准 jq 的一个亮点处理订单号、ID、时间戳等超出float64精确表示范围的值时不会出现尾数失真。五、源码级原理输入读取、输出与取消5.1 输入读取流程readInputsinternal/shell/jq.go 的readInputs负责把所有输入stdin 或多个文件统一读入内存再交给 gojq 执行-n优先直接返回[]any{nil}完全不触碰 stdin文件优先于 stdin有文件参数时逐文件打开读取否则读 stdin-R模式按行切分为字符串配合-s时将所有行用\n拼接为一个整体字符串JSON 流模式使用json.Decoder连续解码支持一个输入流中包含多个 JSON 值这正是jq -s .能 slurp 多个值的基础解码失败报 parse error 并以退出码 2 返回空输入兜底一个值都没读到如空文件时返回[]any{nil}保证 filter 至少执行一次。5.2 输出编码writeValuewriteValue按以下规则输出每个值rawOutput且值为字符串直接写字符串joinOutput时不再追加换行否则追加\ncompact模式使用gojq.Marshal输出单行紧凑 JSON默认模式使用json.MarshalIndent(v, , )输出带两级缩进的漂亮 JSON。5.3 上下文取消hook 超时也能中断长查询这是内置 jq 最值得注意的工程细节。handleJQ在三个位置轮询ctx.Err()入口快速失败ctx 已取消时立即返回不为注定失败的请求付出 flag 解析与 gojq 编译的开销迭代循环内每产出或丢弃一个值都检查一次 ctx因此像range(10000000)这类会生成海量值的 filter 能被及时打断读取期间ctxReader包装底层 reader在每次Read调用前检查 ctx使io.ReadAll在大输入源上也能按块边界取消同时值累积循环raw-input 行切分、JSON 流解码也逐轮检查。被取消时返回的是ctx.Err()本身context.Canceled或context.DeadlineExceeded而不是interp.ExitStatus。这保证了调用方如 hook runner能区分「filter 正常退出非零」与「我们超时了」两种截然不同的情况。internal/shell/jq_test.go 中的四个取消相关测试完整验证了这一设计TestJQ_CtxCancel执行range(10000000)前即取消 ctx断言返回context.CanceledTestJQ_CtxCancel_DuringFilter50ms 超时下运行range(100000000)断言在 1 秒内被中断而非跑完 1 亿次迭代TestJQ_CtxCancel_MidReadAll用 512 字节/5ms 的慢速 reader 喂 64MiB 原始输入证明ctxReader能在流中段观察到取消若被取消则context.Canceled若被完整读完则失败TestJQ_CtxCancel_PreCancel用「一读就 fail」的 reader 证明入口快速失败路径在进入io.ReadAll之前就生效。正如 internal/shell/jq.go 的注释所述如果 reader 本身永久阻塞如未关闭的管道内置 jq 仍可能熬过 ctx此时 hook runner 的 abandon-goroutine 兜底路径见 internal/hooks/runner.go才是最终执行者。六、常用模式实战以下示例均来自 internal/skills/builtin/jq/SKILL.md可直接复制运行。提取字段echo {name:crush} | jq .name过滤数组echo [1,2,3,4,5] | jq [.[] | select(. 3)]重塑对象echo {first:Ada,last:Lovelace} | jq {full: (.first .last)}使用变量字符串与 JSON 值echo {} | jq --arg host localhost --argjson port 8080 {host: $host, port: $port}Slurp 多个 JSON 值echo {a:1}{b:2} | jq -s .紧凑输出便于管道传递echo {a:1} | jq -c .a 1原始字符串输出echo [one,two,three] | jq -r .[]处理文件jq .dependencies | keys package.jsonNull 输入构造 JSONjq -n --arg msg hello {message: $msg}七、实战在 hook 脚本中用 jq 构造结构化输出内置 jq 最典型的高级用法出现在 Crush 的 hook 生态中。以仓库自带的 docs/hooks/examples/rtk-rewrite.sh 为例该 PreToolUse hook 会把 bash 命令重写为 rtk 命令以节省 token其最后一步正是用jq -n --arg构造 hook 返回给 Crush 的结构化 JSONREWRITTEN$(rtk rewrite $CMD 2/dev/null) EXIT_CODE0 || EXIT_CODE$? case $EXIT_CODE in 0 | 3) # Rewrite found. If identical, the command already uses rtk. [ $CMD $REWRITTEN ] exit 0 jq -n --arg cmd $REWRITTEN \ {decision: allow, updated_input: ({command: $cmd} | tostring)} ;; *) # No rewrite (1), deny (2), or unexpected — pass through. exit 0 ;; esac这里jq -n --arg cmd $REWRITTEN用到了本技能的核心组合能力-n忽略 stdinhook 脚本没有 JSON 输入--arg将 shell 变量安全地注入 JSON 字符串自动完成引号转义随后{decision: ...}重塑出 Crush hook 协议要求的allow决策与更新后的工具输入。这与 hook 体系docs/hooks/README.md中「重写工具输入」的用途完全吻合——$CRUSH_TOOL_INPUT_COMMAND等环境变量由 internal/hooks/runner.go 的BuildEnv注入脚本内再用 jq 加工后输出。类似的模式还可以用于提取字段供后续命令使用jq -r .url data.json | xargs curl在 hook 中过滤与统计工具调用日志对$CRUSH_TOOL_INPUT_COMMAND做select/test匹配自动审批安全命令对命令 JSON 做条件判断后输出{decision: allow}。八、实用技巧速查将 jq 输出管道给其他命令jq -r .url data.json | xargs curlfilter 内部用|串联多个变换不要用 shell 管道shell 管道会丢失 JSON 上下文用try抑制缺失键导致的报错jq try .foo.bar用// default提供兜底值jq .name // unknown善用格式化字符串函数csv、tsv、base64、html、uri可将数组/对象直接编码为 CSV、TSV、Base64、HTML 或 URI 组件格式。九、小结Crush 内置 jq 是「零依赖工具链」设计的一个缩影纯 Go 的 gojq 实现通过 builtin 注册机制嵌入 shell既有标准 jq 的核心能力又针对 Agent 场景做了取舍——上下文可中断、参数行为明确、退出码语义清晰还能无缝嵌入 hook 脚本构造结构化输出。对模型而言internal/skills/builtin/jq/SKILL.md 提供了何时调用与如何调用的完整指引对开发者而言internal/shell/jq.go 与其测试则是理解其边界与可靠性的最佳入口。【免费下载链接】crushGlamourous agentic coding for all 项目地址: https://gitcode.com/gh_mirrors/crush3/crush创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考