本教程面向已经能在交互式 REPL 里用 Claude Code 完成单次任务、但想把"调用一次 Claude"变成"流水线上的一颗螺丝钉"的开发者。我们将把 -p(headless/print)模式当作一个标准的 Unix 过滤器来设计——它有 stdin、stdout、exit code,会吐出机器可读的事件流,能在断点处续跑。读完之后,你能够用 jq、xargs、fswatch 把 Claude Code 编进任何 Shell 管道里,并实时观测每一步的 token 与耗时。
claude --version 可正常输出,建议 2.x 及以上)jq(事件流解析必备)、xargs、以及文件监听工具 entr 或 fswatch(macOS)交互式 REPL 是一个"对话",而 -p 是一个"函数调用"。它的设计哲学完全是 Unix 式的:从 stdin 读、向 stdout 写、用 exit code 报告成败。掌握这一点,你就掌握了它和 sed、awk、jq 同级的可组合性。
最朴素的三种调用:
# 1. 提示词作为参数,纯文本输出
claude -p "用一句话解释什么是 PDU 会话"
# 2. 提示词从 stdin 读入(管道左侧喂入"数据")
cat error.log | claude -p "总结这批日志的根因,只输出一句"
# 3. 文件内容重定向进 stdin,参数里只放"指令"
claude -p "为下面的函数补上类型注解,原样输出代码,不要解释" < utils.py
预期结果:三条命令都不会进入 REPL,执行完直接回到 Shell 提示符,答案打印在标准输出。
核心心智模型:把"指令"和"数据"分离。指令写在参数里(稳定、可复用),数据从 stdin 流入(每次不同)。这正是 Unix 过滤器的味道——grep PATTERN 里 PATTERN 是指令,文件流是数据。
常见问题:
| 现象 | 原因 | 解决 |
|---|---|---|
| 输出里夹了"好的,这是结果……"等寒暄 | 默认系统提示词允许对话语气 | 用 --append-system-prompt 强约束:"只输出最终答案,不要寒暄" |
| 多行提示词被 Shell 拆成多个参数 | 没有正确引用 | 用单引号包裹,或改走 stdin |
| 想把答案写文件却连提示符也写进去了 | 直接 claude -p ... > out.txt 会把 stderr 一起混入 |
用 2>/dev/null 或 --output-format 拿干净输出 |
-p 的灵魂在于 --output-format。它有三个取值,对应三个完全不同的使用场景:
# text —— 给人看,纯文本答案
claude -p --output-format text "列出三种排序算法"
# json —— 给脚本用,整个会话压成一个 JSON 对象(阻塞到结束才输出)
claude -p --output-format json "列出三种排序算法" | jq .
# stream-json —— 给实时观测用,逐行吐出事件流(NDJSON)
claude -p --output-format stream-json "列出三种排序算法" \
| jq -c 'select(.type=="assistant")'
stream-json 是最值得深挖的一个。它按行输出(Newline-Delimited JSON),每一行是一个事件对象。理解这四种 type,你就能像读一段协议那样读懂 Claude 的整个思考过程:
sequenceDiagram
participant U as 你的脚本(stdin)
participant C as claude -p
participant M as Claude 模型
U->>C: 提示词 + 数据
C->>U: {type:"system", subtype:"init", session_id, tools, model, permission_mode}
loop 每一轮 agent turn
C->>M: 历史 + 工具结果
M-->>C: assistant 消息(content: text | tool_use)
C->>U: {type:"assistant", message:{content:[...]}}
alt 包含工具调用
C->>C: 执行工具(读文件/跑命令)
C->>U: {type:"user", message:{content:[{type:"tool_result",...}]}}
end
end
C->>U: {type:"result", subtype:"success", total_cost_usd, num_turns, duration_ms, session_id}
C-->>U: exit code 0
system/init 事件里藏着 session_id——立刻把它存下来,这是稍后续跑的钥匙。result 事件里有四个关键字段:num_turns(轮数)、total_cost_usd(花费)、duration_ms(耗时)、is_error(是否出错)。一行 jq 就能把它们抽出来做账:
claude -p --output-format stream-json "重构 utils.py" \
| jq -c 'select(.type=="result") | {turns:.num_turns, cost:.total_cost_usd, ms:.duration_ms, sid:.session_id}'
预期结果:结束时打印一行形如 {"turns":4,"cost":0.0123,"ms":8421,"sid":"abc123…"} 的 JSON。
常见问题:
jq 解析 stream-json 总报错":json 格式是一次性输出整个对象,stream-json 是多行。对后者必须用 jq -c 按行处理,或用 jq --slurp 收成数组。两者别混用。assistant 事件,直接取 result 事件的 .result 字段——它就是最终给用户的纯文本答案,省去拼接多轮 text 的麻烦。0 成功;1 执行过程出错(对应 result.subtype 为 error_during_execution);2 表示撞到 --max-turns 上限(error_max_turns)。脚本里用 $? 判断分支,比解析文本可靠得多。交互式会话天然有状态,但 -p 看似是"一次性"的——其实不然。Claude Code 把每一次 -p 调用都持久化为一条会话记录,配上三个标志位,它就变成了一个可中途暂停、可分叉、可回放的状态机:
# 拿到上一轮的 session_id(从 init 或 result 事件里取)
SID=$(claude -p --output-format stream-json "第一步:分析需求" \
| jq -r 'select(.type=="result") | .session_id')
# continue —— 续接"当前目录下最近的一次"会话,无需记 id
claude --continue -p "第二步:基于刚才的分析出方案"
# resume —— 精确续接指定 session,适合并行多分支任务
claude --resume "$SID" -p "第三步:把方案拆成 TODO"
# fork —— 从某会话分叉出一个新分支,原会话不被污染
claude --fork "$SID" -p "换个思路,试试另一套方案"
这三者的差别很关键:
--continue(-c)是"懒人续跑":它隐式选中 cwd 里最近一次会话。单线任务用最省事,但并发场景下会抢同一条最近会话,导致串台。--resume <id> 是"精确续跑":你显式指定 session_id,多任务并行时各自续各自的,互不干扰。这是批处理与多分支工作流的正确选择。--fork <id> 是"无损分叉":它复制历史并另起一条会话,原会话保持原样。适合"在不破坏主线的前提下探索 Plan B"。stateDiagram-v2
[*] --> S1: claude -p "分析需求"
S1 --> S2: --resume S1 "出方案"
S2 --> S3: --resume S2 "拆 TODO"
S2 --> B1: --fork S2 "方案B"
B1 --> B2: --resume B1 "..."
S3 --> [*]: --resume S3 "执行"
note right of S1: session_id 是状态机的游标
常见问题:
--continue 续跑,但 Claude 像失忆了":多半是 cwd 变了。会话记录是按工作目录绑定的,换目录就续不上。要么在同一目录跑,要么改用 --resume <id> 显式指定。--fork 和 --resume 区别记不住":一句话——resume 是"接着干"(同一条线前进),fork 是"另起一支"(原线原地不动)。担心弄脏主线就用 fork。sid 文件约定,让管道节点之间靠文件传递游标。-p 跑在脚本里,意味着没有人在旁边盯。三个标志位构成了你的"护栏三件套":
claude -p \
--max-turns 8 \
--permission-mode acceptEdits \
--allowedTools "Read,Edit,Bash(npm run *)" \
--append-system-prompt "改完必须跑 npm test,测试不过就回滚,不要狡辩。" \
"修复 src/auth.ts 里的空指针,提交前自测"
--max-turns N:硬上限。Agent 陷入死循环时,N 就是你的救命稻草。给探索性任务留大点(如 20),给确定性任务收窄(如 5)。撞顶会返回 exit code 2,脚本里据此判定"这次没跑完,需要人工介入"。--permission-mode:default(每次问,不适合无人值守)、acceptEdits(自动放行文件编辑,但危险命令仍拦截)、plan(只规划不动手)、bypassPermissions(全自动,慎用,仅隔离环境)。CI 里最常用 acceptEdits。--allowedTools / --disallowedTools:白/黑名单。--allowedTools "Bash(npm run *),Read,Edit" 把 Bash 限定到只能跑 npm run,是命令注入的第一道闸。预期结果:上面这条命令会自动改文件、跑测试,若 8 轮内没收敛就以非零码退出,不会无限烧 token。
常见问题:
bypassPermissions 真省心,我直接全局开":危险。它等于把 Shell 完全交给模型。永远只在容器/沙箱里开,生产机器上用 acceptEdits + --allowedTools 收口。--append-system-prompt 但模型不遵守":把它写成可观测的动作("跑测试""回滚"),而非抽象原则("要小心""保证质量")。模型对动词比对形容词听话。单次 -p 是函数,组合起来才是工程。三个经典 Unix 模式把 Claude 变成管道公民:
模式 A:流式解析——实时观测与过滤
# 只看模型每一次要调用的工具,像 tail -f 一样盯着它干活
claude -p --output-format stream-json "梳理这个仓库的依赖" \
| jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="tool_use") | "调用:" + .name + " " + (.input | tostring)'
模式 B:扇出——一个结果喂给 N 个子任务
# 先让 Claude 列出需要加测试的文件,再对每个文件并发派发子任务
claude -p --output-format text \
--append-system-prompt "只输出文件名,每行一个,不要任何解释" \
"src 目录下哪些模块最缺单元测试?" \
| xargs -P4 -I{} claude -p --max-turns 6 \
--permission-mode acceptEdits \
"为 {} 补三个单元测试"
-P4 让 4 个子任务并发跑。每个 -p 都是独立的进程、独立的会话,天然隔离——这比手写线程池简单得多。
模式 C:监听——文件一变就触发
# 用 entr 监听:只要被测文件改动,就让 Claude 重跑测试并自动修
ls src/*.ts | entr -c sh -c '
claude -p --output-format stream-json --max-turns 10 \
--permission-mode acceptEdits \
"跑 npm test,把第一个失败的用例修到通过" \
| jq -c "select(.type==\"result\") | {ok:(.is_error|not), turns:.num_turns}"
'
macOS 上把 entr 换成 fswatch -o src/ | xargs -I{} ... 即可。
常见问题:
xargs -P4 一开,token 账单飙升":并发不是免费午餐。给每个子任务配 --max-turns 上限,并在 --append-system-prompt 里要求"只改目标文件、别扩散"。entr/fswatch 死循环":模型改了文件→触发监听→又改→又触发。用 entr -c 清屏缓解观感,根本上要给模型加约束:"如果测试已通过,直接报告 PASS,不要再改任何文件"。stream-json 的真正威力是"白盒"。下面这段脚本用一个 tee 把事件流一分为二:一份喂给业务处理,一份喂给一个纯 Shell 写的实时仪表盘,边跑边打印轮数、累计 token、累计花费:
#!/usr/bin/env bash
# dashboard.sh —— 边跑 Claude 边看实时开销
FIFO=/tmp/cc_events_$$
mkfifo "$FIFO"
# 后台:把事件流分类统计,实时刷新
(
turns=0; cost=0; itok=0; otok=0
jq -c '.' < "$FIFO" | while read -r ev; do
t=$(echo "$ev" | jq -r '.type')
case $t in
assistant) echo "$ev" | jq -e '.message.content[]? | select(.type=="tool_use")' >/dev/null \
&& turns=$((turns+1));;
result) cost=$(echo "$ev" | jq -r '.total_cost_usd');
itok=$(echo "$ev" | jq -r '.usage.input_tokens');
otok=$(echo "$ev" | jq -r '.usage.output_tokens');;
esac
printf '\rturns=%-3s cost=$%-7s in=%-7s out=%-7s' "$turns" "$cost" "$itok" "$otok"
done
) &
DASH=$!
# 前台:真正执行任务,事件流 tee 给仪表盘
claude -p --output-format stream-json "$@" > "$FIFO"
kill $DASH 2>/dev/null; rm -f "$FIFO"
echo
跑起来:./dashboard.sh "把 src 下所有 console.log 删掉",你会看到一行不断刷新的指标条。把这段思路接上 curl 推到 Prometheus/Grafana,就是一套现成的 Agent 观测管线。
常见问题:
--output-format json 落到文件,仪表盘只读事件流,两者用命名管道(FIFO)解耦,互不串台。assistant 事件里 .message.content[].type=="tool_use" 的 .input 也统计进去,就能画出"工具调用热度图"。把前面六步串成一个真实项目。目标:监听一份不断追加的 app.log,每当出现新的 ERROR,让 Claude 把它的半结构化文本解析成 JSON、判定严重程度、对 P0 自动尝试修复并 PR,全程可观测。
目录约定:.sid/ 存每个告警分支的会话游标;alerts/ 存结构化结果;dash.log 存实时指标。
#!/usr/bin/env bash
# selfheal.sh —— 日志自愈管道主控
set -euo pipefail
mkdir -p .sid alerts
follow_errors() {
# 模式 C:监听日志新增行,过滤出 ERROR,按指纹去重后触发处理
tail -n0 -F app.log \
| grep --line-buffered 'ERROR' \
| while IFS= read -r line; do
FID=$(echo -n "$line" | shasum | cut -c1-8)
[ -f ".sid/$FID.done" ] && continue # 同一问题只处理一次
handle "$FID" "$line" &
done
}
handle() {
local fid="$1" line="$2"
# 步骤1:解析 + 定级(只读,快,max-turns 收窄)
local parsed sid
parsed=$(echo "$line" | claude -p --output-format json --max-turns 4 \
--append-system-prompt "只输出 JSON:{severity:P0|P1|P2, component, msg, suggested_fix}" \
"把这条日志解析成结构化字段")
sid=$(echo "$parsed" | jq -r '.session_id')
echo "$sid" > ".sid/$fid"
echo "$parsed" | jq -r '.result | fromjson' > "alerts/$fid.json"
echo "[$(date +%T)] $fid 定级完成 sid=$sid"
# 步骤2:仅 P0 才尝试自愈(acceptEdits + 白名单,max-turns 兜底)
if [ "$(jq -r .severity "alerts/$fid.json")" = "P0" ]; then
claude --resume "$sid" -p --max-turns 10 \
--permission-mode acceptEdits \
--allowedTools "Read,Edit,Bash(npm run *),Bash(git *)" \
--output-format stream-json \
--append-system-prompt "修完必须 npm test,绿了再 git commit,最后总结一行。" \
"按 suggested_fix 修复并验证" \
| tee -a dash.log | jq -c 'select(.type=="result") | {fid:"'"$fid"'", ok:(.is_error|not), turns:.num_turns, cost:.total_cost_usd}'
fi
touch ".sid/$fid.done"
}
follow_errors
整体架构与数据流:
flowchart LR
LOG[(app.log)] -->|tail -F grep ERROR| DEDUP{指纹去重<br/>shasum}
DEDUP -->|新问题| PARSE[claude -p --max-turns 4<br/>解析+定级]
DEDUP -->|已处理| SKIP[跳过]
PARSE -->|session_id| SID[(.sid/ 指纹→游标)]
PARSE -->|JSON| ALERTS[(alerts/ 结构化)]
PARSE -->|severity| GATE{P0?}
GATE -->|是| HEAL[claude --resume sid<br/>--acceptEdits --max-turns 10<br/>修复+自测+提交]
GATE -->|否| DONE([归档])
HEAL -->|stream-json tee| DASH[dash.log<br/>实时轮数/花费/成败]
HEAL --> SID2[复用同一游标<br/>保持上下文]
这个案例同时用上了:第二步的 stream-json 协议解析、第三步的 --resume 复用游标(定级和自愈共享上下文)、第四步的 --max-turns + --permission-mode + --allowedTools 三重护栏、第五步的 tail/while/& 并发管道、第六步的 tee 实时观测。每一段都能单独抽出来复用。
验收:手动 echo "[ERROR] db connection refused" >> app.log,几秒内应看到 alerts/<fid>.json 生成;若是 P0(取决于你的系统提示约定),会看到 dash.log 里滚出 result 事件和一行汇总。
常见问题:
shasum 指纹 + .done 标记做去重。指纹可改成"去掉时间戳再哈希"以聚合同类问题。--allowedTools 收紧到只允许特定子命令;要求"npm test 不过就 git checkout 回滚"写进 --append-system-prompt;CI 上再加一道独立的回归测试兜底。handle ... & 换成有界并发,例如 xargs -P4,或用一个简单的信号量(sem)限流。code.claude.com/docs/en/headless —— -p 的权威语义、stream-json 事件字段的完整定义。code.claude.com/docs/en/cli-reference —— 所有标志位(含 --fork、--mcp-config、--input-format)的一手说明。--input-format stream-json:把 -p 变成"喂整段对话、收整段对话"的双向协议,可用任何语言写驱动,完全绕开 SDK——适合做自定义 Agent 调度器。PreToolUse/PostToolUse 钩子接到 stream-json 的工具调用事件上,可实现"每次写文件前自动留快照"的细粒度审计。claude -p --permission-mode acceptEdits 做 PR 自动审查与修复,exit code 直接驱动 workflow 的 if: failure() 分支。-p/--print 头部模式、--output-format、--input-format、--resume/--continue 的官方语义与示例。--fork、--max-turns、--permission-mode、--allowedTools、--append-system-prompt、--mcp-config)的分类速查。text/json/stream-json 三种格式的对比与事件流解析实战。--print 标志如何启用头部模式与结构化输出,含事件类型拆解。把每一次
-p调用都当作可组合、可观测、可续跑的工程原语——Claude Code 就从"一个聊天工具"变成"你 Shell 工具箱里最锋利的那把瑞士军刀"。[!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 等场景全覆盖。
