本教程聚焦 Claude Code 最容易被忽视、却最具工程价值的能力:把 CLI 变成一个输出 NDJSON(换行分隔 JSON)的可编程 Unix 管道组件。你将掌握 --output-format 三种模式的差异、stream-json 协议的消息流拆解、用 jq 实时提取结构化数据、通过 stdin 管道驱动多轮对话,以及用 --resume 捕获 session_id 实现长任务断点续传。适合已能跑通 -p 基础自动化、想构建生产级数据管道的工程师与平台团队。
claude -p "..." 的非交互用法jq(管道数据处理核心工具)-p(即 --print)进入无头模式,真正决定输出形态的是 --output-format。三者区别远不止"换种显示",而是面向完全不同的下游消费方:
| 格式 | 输出形态 | 适用场景 | 典型消费者 |
|---|---|---|---|
text(默认) |
纯文本流,人类可读 | 终端直接查看、简单脚本 | 人眼、grep、tee |
json |
单个 JSON 对象 | 一次性取最终结果 + 元数据 | 脚本变量、CI 日志解析 |
stream-json |
NDJSON 消息流(每行一个 JSON) | 实时事件流、自定义 UI、深度管道编排 | jq 流式、SSE 桥接、事件总线 |
claude -p "用一句话总结当前目录用途" --output-format text
输出就是一段散文。优点是直觉,缺点是任何"提取结论""统计 token""追踪工具调用"都只能靠脆弱的正则。
claude -p "总结当前目录" --output-format json | jq .
输出是单个 JSON 对象,关键字段:
{
"type": "result",
"subtype": "result",
"result": "这是一个用于……的目录",
"session_id": "abc12340-...",
"is_error": false,
"duration_ms": 8421,
"num_turns": 3,
"total_cost_usd": 0.0123,
"usage": { "input_tokens": 1820, "output_tokens": 95 }
}
注意它只给最终态,过程性信息(每一次工具调用、每一轮思考)被折叠掉了。当你只需要"结论 + 成本 + 会话 ID"时,这是最轻量的选择。
典型取值管道:
# 只取纯结论文本
SUMMARY=$(claude -p "总结 README" --output-format json | jq -r '.result')
# 捕获 session_id 供后续 --resume 复用
SID=$(claude -p "做一次代码体检" --output-format json | jq -r '.session_id')
# 成本归集,写入计费表
claude -p "..." --output-format json | jq -c '{cost: .total_cost_usd, turns: .num_turns}' >> cost.log
常见问题: 有些人误以为 json 会按事件流式输出——不会。它是阻塞式的,整个 agent 循环跑完才一次性吐出 JSON。需要"边跑边看"必须用 stream-json。
stream-json 是无头模式最有工程价值的格式:它把 agent 的内部循环"事件化",每一行是一条 NDJSON 消息。理解消息类型是构建管道的前提。
claude -p "查看 src/ 下的文件结构" \
--output-format stream-json --verbose
你将看到若干类消息依次落地(每行一个 JSON):
system 消息(握手初始化)subtype 为 init,每条流的第一条,携带运行环境:
{
"type": "system",
"subtype": "init",
"session_id": "abc12340-1111-2222-3333-...",
"tools": ["Read","Grep","Glob","Bash","Edit", ...],
"cwd": "/home/gprshome/project",
"permissionMode": "default",
"model": "claude-sonnet-4-6",
"version": "2.x.x"
}
工程价值: 这条消息里就有 session_id。无需等到 result,管道一开始就能拿到会话句柄,适合"先登记、后回填"的审计设计。
assistant 消息(Claude 的产出)携带 content 数组,可能同时包含文本块和工具调用块:
{
"type": "assistant",
"message": {
"role": "assistant",
"content": [
{ "type": "text", "text": "我先看看目录结构。" },
{ "type": "tool_use", "id": "toolu_01...", "name": "Glob", "input": { "pattern": "src/**/*" } }
]
},
"session_id": "abc12340-..."
}
user 消息(工具执行结果回填)注意:在 stream-json 协议里,工具的返回值以 user 角色消息出现(代表系统侧注入的工具结果):
{
"type": "user",
"message": {
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01...", "content": "src/a.ts\nsrc/b.ts" }
]
},
"session_id": "abc12340-..."
}
result 消息(终态汇总)与 --output-format json 的对象结构一致,作为流的最后一条收尾,含成本、耗时、token、is_error。
stream-event(可选,细粒度 token 流)配合 --include-partial-messages 使用时,会额外出现 token 级别的增量事件,适合自研"打字机"前端。
关键认知: assistant 与 user 在这里成对出现,构成 agent 循环的一轮(turn)。num_turns 等于这个循环的次数。
stream-json + jq 是黄金组合。下面几个管道在实战中复用率极高。
result 是最后一条且 type=="result":
claude -p "分析 src/ 下的架构问题" --output-format stream-json --verbose \
| jq -r 'select(.type=="result") | .result'
claude -p "重构 utils.ts" --output-format stream-json --verbose \
| jq -c 'select(.type=="assistant")
| .message.content[]
| select(.type=="tool_use")
| {tool: .name, input: .input}'
输出示例(每行一次工具调用):
{"tool":"Read","input":{"file_path":"src/utils.ts"}}
{"tool":"Grep","input":{"pattern":"TODO","output_mode":"count"}}
{"tool":"Edit","input":{"file_path":"src/utils.ts","old_string":"...","new_string":"..."}}
这是构建"AI 行为可观测性"的最廉价方案——一行 jq 就能把整个工具调用序列落盘成结构化日志。
claude -p "大型重构任务" --output-format stream-json --verbose \
| jq -c 'select(.type=="result")
| {cost: .total_cost_usd,
in_tokens: .usage.input_tokens,
out_tokens: .usage.output_tokens,
turns: .num_turns,
seconds: (.duration_ms/1000|floor)}'
claude -p "..." --output-format stream-json --verbose \
| jq -r 'select(.type=="system" and .subtype=="init") | .session_id'
常见问题: 忘了加 --verbose 时,stream-json 仍能工作,但部分辅助事件会缺失。涉及深度解析时,养成 stream-json 与 --verbose 成对出现的习惯。
默认 -p 是"一问一答"。要在一个进程内驱动多轮对话,用 --input-format stream-json 从 stdin 逐行喂数据。每行一个 JSON,type 为 user:
{
echo '{"type":"user","message":{"role":"user","content":"读取 config.yaml 并解释每个字段"}}'
sleep 0.2
echo '{"type":"user","message":{"role":"user","content":"现在只保留生产环境必填项"}}'
} | claude --input-format stream-json --output-format stream-json --verbose
协议要点:
user 消息,Claude 完成一轮 agent 循环并产出对应的 assistant/user/result 流,再等待下一条。claude 实例(见第六部分)。典型用法——脚本驱动的多步工作流:
# steps.ndjson 预先编排好追问序列
cat steps.ndjson \
| claude --input-format stream-json --output-format stream-json --verbose \
| jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="text") | .text'
这让 Claude Code 变成一个可被任意脚本驱动、输出全程结构化的后端引擎。
长任务可能因为网络抖动、token 超限、误操作而中断。会话恢复是无头模式生产化的关键。
两个核心标志位:
--continue(或 -c):恢复最近一次会话--resume <session_id>(或 -r):恢复指定会话而 session_id 在无头模式下从哪里拿?正是 json/stream-json 输出里的 .session_id 字段。这构成了一个闭环:
# 第一步:跑一个长任务,捕获会话句柄
SID=$(claude -p "开始大规模代码审计" --output-format json | jq -r '.session_id')
echo "$SID" > .claude_session
# ——中途网络断了 / 进程被杀——
# 第二步:用相同句柄续跑,上下文完整保留
claude --resume "$(cat .claude_session)" \
-p "继续上次的审计,输出风险报告" \
--output-format json | jq -r '.result'
生产实践: 在 CI/批处理脚本里,每次启动都把 session_id 写入 runs/<run_id>.session,失败重试时优先 --resume。这样 token 与上下文不浪费,且可追溯。
RUN_ID="${1:-$(date +%s)}"
SESSION_FILE="runs/${RUN_ID}.session"
mkdir -p runs
if [[ -f "$SESSION_FILE" ]]; then
# 续跑模式
claude --resume "$(cat "$SESSION_FILE")" -p "$TASK" \
--output-format stream-json --verbose \
| tee "runs/${RUN_ID}.ndjson" \
| jq -r 'select(.type=="result") | .result'
else
# 新会话,登记句柄
claude -p "$TASK" --output-format stream-json --verbose \
| tee "runs/${RUN_ID}.ndjson" \
| jq -r 'select(.type=="system" and .subtype=="init") | .session_id' > "$SESSION_FILE"
fi
常见问题:
--continue 只认"最近",并发场景会串台,多任务务必用显式 --resume <id>。session_id 存在于本地会话存储(~/.claude/projects/<encoded-cwd>/ 下的 jsonl),跨机器不能直接迁移,需配合会话目录同步。--max-turns N 防止失控。把前面四块拼起来,Claude Code 就能像 Unix 小工具一样被 | 串联。核心思路:上游实例的结论文本,通过管道变成下游实例的 prompt 输入。
# 第一段:体检
claude -p "审计 src/ 给出最严重的3个问题(JSON数组)" --output-format json \
| jq -r '.result' \
| claude -p "针对这些问题,为每个生成修复方案" --output-format json \
| jq -r '.result' \
| claude -p "把方案转成可执行的 git commit 计划" --output-format json \
| jq -r '.result'
每个 claude 是一个纯函数:prompt_in → result_out,组合性天然。
用 xargs -P 或 GNU parallel 对多个目标并行分析,最后汇总:
# 对每个 src 子模块并行生成摘要
find src -maxdepth 1 -mindepth 1 -type d \
| xargs -I{} -P4 bash -c '
claude -p "分析 {} 目录的职责,用一句话概括" --output-format json \
| jq -r --arg d "{}" "{dir: \$d, summary: .result}"
' \
| jq -s '.' > module_map.json
# 汇总成架构报告
claude -p "$(jq -r '.[] | "\(.dir): \(.summary)"' module_map.json)
基于以上模块职责,输出整体架构建议" --output-format json | jq -r '.result'
注意成本控制: 并行扇出会线性放大 token。务必给每个子任务 --max-turns 限制,并在每个实例后用 jq 打印成本累计。
把全部技术点集成进一个真实脚本 impact-analyze.sh——给定一个 PR 的 diff,产出结构化影响报告,全程 NDJSON 可观测、可断点续传。
#!/usr/bin/env bash
set -euo pipefail
DIFF_FILE="${1:?用法: impact-analyze.sh <diff.patch> [run_id]}"
RUN_ID="${2:-$(date +%s)}"
RUN_DIR="runs/$RUN_ID"
mkdir -p "$RUN_DIR"
# 1. 构造分析 prompt,把 diff 喂进去
PROMPT=$(cat <<EOF
你是资深架构师。下面是一个 PR 的 diff,请输出 JSON,字段:
- risk_level: low|medium|high
- affected_modules: 字符串数组
- breaking: boolean
- recommendations: 字符串数组
只输出 JSON,不要解释。
=== DIFF ===
$(cat "$DIFF_FILE")
EOF
)
# 2. 跑分析,落盘完整事件流,实时提取结论
claude -p "$PROMPT" \
--output-format stream-json --verbose \
--max-turns 6 \
| tee "$RUN_DIR/stream.ndjson" \
| jq -r 'select(.type=="result") | .result' > "$RUN_DIR/report.json"
# 3. 从握手消息登记 session_id(供失败续跑)
jq -r 'select(.type=="system" and .subtype=="init") | .session_id' \
"$RUN_DIR/stream.ndjson" > "$RUN_DIR/session.id"
# 4. 成本与行为审计落盘
jq -c 'select(.type=="assistant")
| .message.content[]?
| select(.type=="tool_use")
| .name' "$RUN_DIR/stream.ndjson" > "$RUN_DIR/tools.trace"
jq -c 'select(.type=="result")
| {cost:.total_cost_usd, turns:.num_turns,
in:.usage.input_tokens, out:.usage.output_tokens}' \
"$RUN_DIR/stream.ndjson" > "$RUN_DIR/metrics.json"
echo "✅ 完成。报告: $RUN_DIR/report.json | 续跑句柄: $(cat "$RUN_DIR/session.id")"
为什么这个脚本是"生产级"的:
stream.ndjson 留存全量事件,事后任意维度复盘。session.id 让中断后能 --resume 接着干。metrics.json 直接喂进成本看板。tools.trace 记录每一次工具调用,满足合规复盘。--max-turns 6 兜底,防止 agent 失控烧 token。flowchart LR
subgraph 上游["上游输入"]
T1["脚本/CI"]
T2["stdin NDJSON"]
T3["会话句柄 session.id"]
end
subgraph 引擎["Claude Code 无头引擎"]
direction TB
INIT["system/init<br/>→ session_id"]
LOOP["assistant→user<br/>agent 循环 N 轮"]
END["result<br/>→ 成本/token/结论"]
end
T2 -->|input-format stream-json| 引擎
T3 -->|resume 句柄| 引擎
T1 -->|-p prompt| 引擎
INIT -->|jq init| SBOX[("session.id 落盘")]
引擎 -->|output-format stream-json --verbose| RAW[("stream.ndjson 全量流")]
RAW --> JQ1["jq select result<br/>.result"]
RAW --> JQ2["jq tool_use<br/>行为审计"]
RAW --> JQ3["jq usage<br/>成本看板"]
JQ1 --> RPT["report.json 结论"]
JQ2 --> TRACE["tools.trace"]
JQ3 --> METRICS["metrics.json"]
RPT --> DOWN["下游消费者:<br/>CI 门禁 / 看板 / 另一个 claude"]
SBOX -.断点续传.-> 引擎
Q1:stream-json 输出看着像 JSON 但 jq 报错?
A:它是 NDJSON(每行一个独立 JSON),不是单个 JSON 数组。直接 jq 过滤即可(jq 天然按行处理),不要用 jq -s '.' 包成数组后再处理,除非确实要全量聚合。
Q2:管道里 claude 卡住不退出?
A:多半是 --input-format stream-json 模式下 stdin 没关闭,进程在等下一条消息。确保上游 echo 完毕或管道 EOF 正常触发。
Q3:--resume 报"会话不存在"?
A:会话存储按工作目录隔离(~/.claude/projects/<encoded-cwd>/)。续跑时 cwd 必须与原会话一致;跨目录续跑会失败。
Q4:并行多个实例互相串话?
A:无头实例各自独立,但共享本地文件系统与会话目录。并行写同一文件会冲突;--continue 会抢"最近会话"。并发场景一律用 --resume <显式id> + 隔离的 --add-dir / 工作目录。
Q5:成本炸了怎么防?
A:三层兜底——① 每实例 --max-turns N;② --allowedTools 收窄工具集;③ 管道尾部 jq 累计 total_cost_usd,脚本里设阈值熔断。
Q6:JSON 里 result 字段含中文乱码?
A:确保终端与落盘文件 UTF-8。jq -r 输出中文无需特殊处理,但 tee 到的文件若被 iconv 过会乱。链路里别混入非 UTF-8 转码。
code.claude.com/docs/en/headless — --output-format/--input-format/--resume 的权威定义code.claude.com/docs/en/cli-reference — 全部标志位速查github.com/claudification/claudewerk 的 docs/stream-json-protocol.md — 消息类型穷举github.com/badlogic/cc-wrap 的 docs/claude-stream-json.md — 详尽事件类型参考docs.bswen.com/blog/2026-03-21-stream-json-custom-ui-claude-code — 把 stream-json 接进自有前端jq 基本语法,已跑通过 claude -p--output-format、--input-format、--resume/--continue 官方权威定义[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
