<!-- title: 无头模式结构化输出与管道编排 -->
```
<!-- title: 无头模式结构化输出与管道编排 -->
```

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

## 概述

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

## 前置条件

- Claude Code 已安装且可交互运行(本文基于 2.x 版本特性)
- 基础熟悉 `claude -p "..."` 的非交互用法
- 已安装 `jq`(管道数据处理核心工具)
- Shell 为 bash 或 zsh(脚本用到 heredoc 与进程替换)
- 一台可联网、能消耗 API 配额的开发机

---

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

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

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

### text:人类友好但难以解析

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

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

### json:一次性结构化快照

```bash
claude -p "总结当前目录" --output-format json | jq .
```

输出是**单个** JSON 对象,关键字段:

```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"时,这是最轻量的选择。

**典型取值管道:**

```bash
# 只取纯结论文本
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 消息。理解消息类型是构建管道的前提。

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

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

### 1. `system` 消息(握手初始化)

subtype 为 `init`,**每条流的第一条**,携带运行环境:

```json
{
  "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` 数组,可能同时包含文本块和工具调用块:

```json
{
  "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` 角色消息出现(代表系统侧注入的工具结果):

```json
{
  "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"`:

```bash
claude -p "分析 src/ 下的架构问题" --output-format stream-json --verbose \
  | jq -r 'select(.type=="result") | .result'
```

### 追踪每一次工具调用(行为审计)

```bash
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 看板

```bash
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

```bash
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`:

```bash
{
  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` 流,再等待下一条。
- 管道关闭(stdin EOF)后,进程优雅退出。
- 上游可以是另一个程序、一个文件,甚至另一个 `claude` 实例(见第六部分)。

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

```bash
# 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` 字段**。这构成了一个闭环:

```bash
# 第一步:跑一个长任务,捕获会话句柄
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 与上下文不浪费,且可追溯。

```bash
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 输入**。

### 串行管道(分析 → 决策 → 执行)

```bash
# 第一段:体检
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` 对多个目标并行分析,最后汇总:

```bash
# 对每个 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 可观测、可断点续传。

```bash
#!/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。

---

## 八、整体数据流架构

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

---

## 延伸阅读

- **Claude Code 官方 Headless 文档**:`code.claude.com/docs/en/headless` — `--output-format`/`--input-format`/`--resume` 的权威定义
- **CLI Reference**:`code.claude.com/docs/en/cli-reference` — 全部标志位速查
- **stream-json 协议参考**:`github.com/claudification/claudewerk` 的 `docs/stream-json-protocol.md` — 消息类型穷举
- **cc-wrap 文档**:`github.com/badlogic/cc-wrap` 的 `docs/claude-stream-json.md` — 详尽事件类型参考
- **构建自定义 UI**:`docs.bswen.com/blog/2026-03-21-stream-json-custom-ui-claude-code` — 把 stream-json 接进自有前端

---

## 训练营适用性评估

- **适用类型:** 平台工程 / DevOps 自动化 / AI 工程化 训练营;中后端工程师进阶班
- **学员基础:** 需熟悉 Shell 管道、`jq` 基本语法,已跑通过 `claude -p`
- **教学时长:** 建议 **4 学时**——输出格式对比(0.5h)、协议拆解与 jq 实战(1.5h)、多轮管道与会话恢复(1h)、完整案例手敲与排障(1h)
- **考核点:** 能独立写出"全量落盘 + 会话续跑 + 成本审计"三位一体的无头脚本
- **进阶衔接:** 本教程是后续 Hooks 门禁、CI/CD 集成、多 Agent 编排的**数据层基石**——任何上层编排都建立在"能稳定提取结构化输出"之上

---

## 参考来源

1. [Run Claude Code programmatically — Claude Code Docs](https://code.claude.com/docs/en/headless) — 无头模式、`--output-format`、`--input-format`、`--resume`/`--continue` 官方权威定义
2. [Claude Code Stream-JSON Protocol Reference — GitHub(claudewerk)](https://github.com/claudification/claudewerk/blob/main/docs/stream-json-protocol.md) — NDJSON 消息类型与事件流穷举
3. [Claude Code stream-json: the output format that changes everything — backgroundclaude](https://backgroundclaude.com/blog/stream-json) — stream-json 工程价值与实战视角
4. [CLI Output & Headless Mode — DeepWiki(kdxsydq/ClaudeCode)](https://deepwiki.com/kdxsydq/ClaudeCode/14.2-cli-output-and-headless-mode) — 非交互输出管道与字段拆解
5. [Mastering Claude Code Sessions: -continue & -resume — aiopsschool](https://aiopsschool.com/blog/mastering-claude-code-sessions-continue-resume/) — 会话持久化在 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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
