首页 › tutorials › 无头模式结构化输出与管道编排

Claude Code 无头模式结构化输出与管道编排实战

概述

本教程聚焦 Claude Code 最容易被忽视、却最具工程价值的能力:把 CLI 变成一个输出 NDJSON(换行分隔 JSON)的可编程 Unix 管道组件。你将掌握 --output-format 三种模式的差异、stream-json 协议的消息流拆解、用 jq 实时提取结构化数据、通过 stdin 管道驱动多轮对话,以及用 --resume 捕获 session_id 实现长任务断点续传。适合已能跑通 -p 基础自动化、想构建生产级数据管道的工程师与平台团队。

前置条件


一、三种输出格式的本质差异

-p(即 --print)进入无头模式,真正决定输出形态的是 --output-format。三者区别远不止"换种显示",而是面向完全不同的下游消费方:

格式 输出形态 适用场景 典型消费者
text(默认) 纯文本流,人类可读 终端直接查看、简单脚本 人眼、grep、tee
json 单个 JSON 对象 一次性取最终结果 + 元数据 脚本变量、CI 日志解析
stream-json NDJSON 消息流(每行一个 JSON) 实时事件流、自定义 UI、深度管道编排 jq 流式、SSE 桥接、事件总线

text:人类友好但难以解析

claude -p "用一句话总结当前目录用途" --output-format text

输出就是一段散文。优点是直觉,缺点是任何"提取结论""统计 token""追踪工具调用"都只能靠脆弱的正则。

json:一次性结构化快照

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 协议逐字段拆解

stream-json 是无头模式最有工程价值的格式:它把 agent 的内部循环"事件化",每一行是一条 NDJSON 消息。理解消息类型是构建管道的前提。

claude -p "查看 src/ 下的文件结构" \
  --output-format stream-json --verbose

你将看到若干类消息依次落地(每行一个 JSON):

1. 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,管道一开始就能拿到会话句柄,适合"先登记、后回填"的审计设计。

2. 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-..."
}

3. 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-..."
}

4. result 消息(终态汇总)

与 --output-format json 的对象结构一致,作为流的最后一条收尾,含成本、耗时、token、is_error。

5. stream-event(可选,细粒度 token 流)

配合 --include-partial-messages 使用时,会额外出现 token 级别的增量事件,适合自研"打字机"前端。

关键认知: assistant 与 user 在这里成对出现,构成 agent 循环的一轮(turn)。num_turns 等于这个循环的次数。


三、jq 管道实战:从事件流提取结构化洞察

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 就能把整个工具调用序列落盘成结构化日志。

实时成本与 token 看板

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)}'

提取握手即得的 session_id

claude -p "..." --output-format stream-json --verbose \
  | jq -r 'select(.type=="system" and .subtype=="init") | .session_id'

常见问题: 忘了加 --verbose 时,stream-json 仍能工作,但部分辅助事件会缺失。涉及深度解析时,养成 stream-json 与 --verbose 成对出现的习惯。


四、stdin 驱动多轮对话管道

默认 -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

协议要点:

典型用法——脚本驱动的多步工作流:

# 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 超限、误操作而中断。会话恢复是无头模式生产化的关键。

两个核心标志位:

而 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

常见问题:


六、多实例串联:真正的管道编排

把前面四块拼起来,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")"

为什么这个脚本是"生产级"的:


八、整体数据流架构

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 转码。


延伸阅读


训练营适用性评估


参考来源

  1. Run Claude Code programmatically — Claude Code Docs — 无头模式、--output-format、--input-format、--resume/--continue 官方权威定义
  2. Claude Code Stream-JSON Protocol Reference — GitHub(claudewerk) — NDJSON 消息类型与事件流穷举
  3. Claude Code stream-json: the output format that changes everything — backgroundclaude — stream-json 工程价值与实战视角
  4. CLI Output & Headless Mode — DeepWiki(kdxsydq/ClaudeCode) — 非交互输出管道与字段拆解
  5. Mastering Claude Code Sessions: -continue & -resume — aiopsschool — 会话持久化在 CI/CD 自动化中的捕获与重放模式

[!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 等场景全覆盖。

51学通信知识星球海报-扫码加入