本教程教你把 Claude Code 当作一个标准的 Unix 管道工具来用:用 --bare 隔离本地配置保证可复现,用 -p 无头模式接收 stdin、吐出 stdout,用 --output-format json 拿到带成本与 session 元数据的结构化结果,再用 --resume 断点续跑、用 xargs -P 并发、用 hooks 做监控告警。适合写过 shell 脚本、想把 AI 能力嵌进 CI/批处理流水线的后端、运维和平台工程师。
npm install -g @anthropic-ai/claude-code,建议 v2.1.182 以上,本教程引用了若干该版本前后的行为细节)jq、git、bash 的 Linux/macOS 机器ANTHROPIC_API_KEY 或完成 OAuth 登录(注意:--bare 模式下不读 OAuth/keychain,必须用 ANTHROPIC_API_KEY,下文详述)|、<、$(...)、xargs)--bare 是批量任务的生命线很多人写自动化脚本的第一反应是直接 claude -p "...",跑一次成了,写进 CI 第二天却挂在同事的机器上。原因在于:普通的 -p 调用会加载和你交互式会话一模一样的上下文——项目里的 .mcp.json、你 ~/.claude 下装的某个 hook、队友提交的一段 CLAUDE.md、自动 memory,全部都会被读进来。
这对"在每台机器上得到相同结果"是致命的。--bare 就是为此而生:
# 普通无头:会吸收本地一切配置,不可复现
claude -p "总结这个项目"
# 裸模式:跳过 hooks/skills/plugins/MCP/auto-memory/CLAUDE.md 的自动发现
claude --bare -p "总结这个项目"
官方文档明确写道:--bare 是脚本和 SDK 调用的推荐模式,未来会成为 -p 的默认行为。它的核心契约有三条,必须牢记:
~/.claude 里的 hook、项目的 .mcp.json、队友的 CLAUDE.md 全部不会执行。只有你显式通过 flag 传入的东西才生效。ANTHROPIC_API_KEY 环境变量,或通过 --settings 传入的 apiKeyHelper。如果你在 CI 里忘了设这个变量,--bare 模式会直接报错退出,而不是弹出一个永远没人去点的登录提示——这正是无人值守场景想要的"快速失败"。需要把上下文显式喂回去时,用下表对应的 flag:
| 你想注入的内容 | 对应 flag |
|---|---|
| 系统提示追加 | --append-system-prompt "..." 或 --append-system-prompt-file <path> |
| 整份 settings | --settings <file-or-json> |
| MCP 服务器 | --mcp-config <file-or-json> |
| 自定义子代理 | --agents <json> |
| 插件 | --plugin-dir <path> 或 --plugin-url <url> |
预期结果:同一段 claude --bare -p ... 命令,在你的笔记本、同事的台式机、CI 的容器里行为一致;换台机器不会再"莫名其妙多读了点东西"。
常见问题:脚本在本地能调 MCP 工具,上了 CI 却报"工具不存在"——因为 --bare 没加载 .mcp.json。解决:用 --mcp-config ./mcp.json 显式带上。
-p 模式天然读取 stdin,于是 Claude 就成了一个可以和 cat、grep、jq 自由串联的管道节点:
# 把构建报错喂给 Claude,把解释写进文件
cat build-error.txt | claude --bare -p '用一句话指出这个构建错误的根因' > output.txt
这里有一个工程上很优雅的写法,把"读文件"这件事交给管道本身,从而完全不需要给 Claude 授予 Bash 权限:
// package.json
{
"scripts": {
"lint:claude": "git diff main | claude --bare -p \"你是一个错别字检查器。对这段 diff 里每个错别字,第一行输出 文件名:行号,第二行输出问题,其余什么都不要输出。\""
}
}
把 diff 通过管道喂进去,Claude 既不需要 Bash 权限去 git diff,脚本也更安全、更可移植(Windows 下双引号转义也保持一致)。
关键限制(踩坑点):从 v2.1.128 起,通过管道传入的 stdin 上限是 10MB。超过会以非零状态码退出并报清晰错误。处理更大输入时,不要硬塞 stdin,而是把内容写到文件、在 prompt 里引用路径:
# 错误:超大日志可能撑爆 stdin 上限
cat huge.log | claude --bare -p "分析这份日志"
# 正确:引用文件路径,让 Claude 自己按需读取
claude --bare -p "分析 ./huge.log 里最近一小时的 ERROR" --allowedTools "Read,Grep"
--output-format json 拿到结构化结果与成本文本输出对人友好,但对脚本不友好。换成 JSON,你会同时拿到结果正文、session ID、以及每次调用的成本:
claude --bare -p "用三句话总结这个项目的架构" --output-format json > summary.json
返回结构大致如下(字段以实际版本为准):
{
"type": "result",
"subtype": "success",
"result": "该项目采用分层架构……(正文)",
"session_id": "abc12340-aaaa-bbbb-cccc",
"total_cost_usd": 0.0123,
"usage": { "input_tokens": 2048, "output_tokens": 312, "cache_read_input_tokens": 1024 },
"duration_ms": 4200,
"num_turns": 1
}
--output-format 有三种取值:
text(默认):纯文本,适合人读json:一次性返回完整 JSON,含 result、session_id、total_cost_usd 等stream-json:换行分隔的 JSON 流,适合实时处理长任务的中间事件(工具调用、思考过程)total_cost_usd 和按模型拆分的成本明细是批处理场景的金矿——脚本可以不查用量看板,直接在本地累计每次调用的花费:
# 批量跑 10 个仓库,累计总成本
total=0
for repo in repos/*; do
cost=$(claude --bare -p "审查 $repo 里的安全风险" \
--output-format json | jq '.total_cost_usd')
total=$(echo "$total + $cost" | bc)
echo "$repo 花费 \$$cost,累计 \$$total"
done
如果你要下游程序按固定字段解析,再加一个 --json-schema 让输出严格符合 schema,把"模型自由发挥的文本"变成"机器可信赖的数据":
claude --bare -p "列出这个仓库所有公开 API 端点" \
--output-format json \
--json-schema '{"type":"object","properties":{"endpoints":{"type":"array","items":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"}},"required":["method","path"]}}},"required":["endpoints"]}'
--resume 实现断点续跑批量任务最怕跑到第 9 个挂了,前 8 个的上下文全丢、得从头再来。Claude Code 的 session 机制正好能治这个病。
先给会话起个有意义的名字(-n):
claude -n "techdebt-audit-repoA" -p "扫描这个仓库的技术债" --output-format json
从 -p 创建的会话不会出现在交互式的 session 选择器里,但你可以拿到它的 session_id(JSON 输出里有),然后在下一次调用里用 --resume <session-id> 把它接回来——必须在创建该会话的同一个目录里执行(session 查找范围限定在当前项目目录及其 git worktree)。
# 第一次:跑一个大任务,拿到 session_id
SID=$(claude -n "big-refactor" -p "把所有 class 组件改成函数组件,先做 src/components" \
--output-format json | jq -r '.session_id')
# 中途断了……接回来继续,带着之前的全部上下文
claude --resume "$SID" -p "继续处理 src/pages 目录" --output-format json
四个入口点的区别,务必记清:
| 命令 | 作用 |
|---|---|
claude --continue |
恢复当前目录里最近的那个会话 |
claude --resume |
打开交互式 session 选择器 |
claude --resume <name> |
按名字直接恢复(跨同一仓库的 worktree 解析) |
claude --resume <session-id> |
按 ID 恢复(-p 创建的会话只能用这条) |
claude --from-pr <number> |
恢复与某个 PR 关联的会话 |
工程含义:批处理脚本应当把每个子任务的 session_id 持久化到文件,失败重试时优先 --resume,而不是重新发起一个干净的调用——既省 token(命中之前的 prompt cache),又能让模型"记得"它上次的判断。
xargs -P 把串行循环升级为并发批处理for 循环是串行的:20 个仓库要排着队跑,白白浪费时间。xargs -P 能并发 N 路 Claude:
# 把 20 个仓库的路径喂给 xargs,最多 4 路并发
printf '%s\n' repos/* | xargs -I{} -P4 bash -c '
repo="$1"
claude --bare -p "审查 $repo 的依赖许可证合规性,输出 JSON" \
--output-format json > "reports/$(basename $repo).json"
' _ {}
要点:
-P4 控制并发度。并发度不是越高越好——它会撞上你的 API 速率限制(RPM/TPM),还会让 total_cost_usd 在同一分钟内飙升。建议从 2~4 起步,观察 429 错误后再加。jq -s 聚合:jq -s '.' reports/*.json > combined.json--bare 在这里是刚需:并发场景下你绝不希望某个调用意外读到了另一个调用的临时状态,--bare 保证每次调用都是从零开始的纯净环境。并发 + 续跑组合,就是健壮批处理的两根支柱:并发省时间,续跑防丢失。
当 -p 调用里 Claude 启动了一个后台 Bash 任务(比如 npm run dev 或一个 watch 构建),它不会立刻被杀——Claude 返回最终结果后,大约有 5 秒宽限期,让刚好在结果之后完成的任务还能把输出送回来,随后那个 shell 被终止。stdin 关闭后整个 -p 进程才真正退出。
两个版本相关的细节:
-p 进程无限期挂起。可以用环境变量 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS 调整,设为 0 表示无限等待。claude -p 调用无限期挂住——如果你的 CI 任务莫名其妙永远不结束,先升级版本,再检查是不是有 daemon 没清理。# 给后台 agent 等待封顶设为 5 分钟
export CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=300000
实战建议:批处理脚本里不要让 Claude 启动长驻后台进程。需要跑测试,就让它前台跑完、拿到退出码;需要 watch,就拆成独立的 cron/CI job。把 -p 调用保持在"有明确终点"的形态,是无人值守稳定性的前提。
-p 模式同样会触发 hooks(除非用 --bare 显式跳过)。批处理最该用三个事件:
// .claude/settings.json(批处理脚本专用项目配置)
{
"hooks": {
"SessionStart": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "echo \"[$(date)] 任务开始 session=$CLAUDE_SESSION_ID\" >> /var/log/batch.log"
}]
}],
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "jq -r '\"任务完成,花费 $\\(.total_cost_usd)\"' >> /var/log/batch.log"
}]
}],
"Notification": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "curl -s -X POST $SLACK_WEBHOOK -d '{\"text\":\"Claude 需要你注意\"}'"
}]
}]
}
}
三个事件的分工:
SessionStart(每会话一次):记录"任务开始了",落审计日志。Stop(每轮一次):Claude 结束响应时触发,input 里有 total_cost_usd,正好记账。Notification:当 Claude Code 需要推送通知(比如等待权限确认、空闲提醒)时触发——批处理里看到这个事件通常意味着任务卡住了等人工,应当立刻推 Slack/钉钉,否则会一直挂着。注意:这三类事件的 cadence 不同。
SessionStart/SessionEnd每个会话一次;UserPromptSubmit/Stop/StopFailure每轮一次;PreToolUse/PostToolUse每次工具调用一次。批处理成本核算放Stop最准,放SessionEnd会漏掉多轮调用。但如果你用了--bare,这些项目级 hooks 默认不会执行——要么改用--settings显式注入,要么把日志逻辑写进 shell 脚本本身。
把上面七步揉成一个完整可运行的脚本 repo-audit.sh。它遍历 repos/ 下所有仓库,并发跑技术债扫描,每个仓库单独出 JSON 报告,失败的自动续跑,最后聚合成本并汇总高危项。
#!/usr/bin/env bash
# repo-audit.sh —— 批量技术债体检
set -uo pipefail
REPOS_DIR="./repos"
REPORTS_DIR="./reports"
STATE_FILE="./reports/state.tsv" # 记录 repo -> session_id 的映射,用于续跑
PARALLEL="${PARALLEL:-3}"
MAX_RETRIES=2
mkdir -p "$REPORTS_DIR"
touch "$STATE_FILE"
audit_one() {
local repo="$1"
local name
name=$(basename "$repo")
local report="$REPORTS_DIR/$name.json"
# 已有报告则跳过(幂等)
[[ -f "$report" ]] && { echo "skip $name (已有报告)"; return 0; }
# 查是否有上次中断的 session_id,有则续跑
local sid=""
sid=$(awk -F'\t' -v r="$name" '$1==r{print $2}' "$STATE_FILE")
local cmd=(-p "扫描这个仓库的技术债:1) 标记所有 TODO/FIXME/HACK 并按严重度分级;
2) 找出圈复杂度最高的 5 个函数;3) 输出按季度排期的整改建议。
严格按 JSON schema 输出。"
--output-format json
--json-schema '{"type":"object","properties":{"todo_count":{"type":"number"},"hotspots":{"type":"array","items":{"type":"string"}},"schedule":{"type":"array","items":{"type":"object","properties":{"quarter":{"type":"string"},"item":{"type":"string"}}}}},"required":["todo_count","hotspots","schedule"]}'
--allowedTools "Read,Grep,Glob")
if [[ -n "$sid" ]]; then
cmd=(--resume "$sid" "${cmd[@]}")
fi
# 带超时与重试
local attempt=0
while [[ $attempt -le $MAX_RETRIES ]]; do
attempt=$((attempt+1))
if timeout 600 claude --bare "${cmd[@]}" > "$report.tmp" 2>"$report.err"; then
# 成功:记录 session_id 以备未来续跑,落正式报告
local new_sid
new_sid=$(jq -r '.session_id // empty' "$report.tmp")
[[ -n "$new_sid" ]] && sed -i "/^$name\t/d" "$STATE_FILE" && printf "%s\t%s\n" "$name" "$new_sid" >> "$STATE_FILE"
mv "$report.tmp" "$report"
local cost
cost=$(jq -r '.total_cost_usd // 0' "$report")
echo "ok $name (成本 \$$cost)"
return 0
fi
echo "retry $name (第 $attempt 次)" >&2
sleep 5
done
echo "FAIL $name" >&2
return 1
}
export -f audit_one
export REPORTS_DIR STATE_FILE MAX_RETRIES
# 并发跑
printf '%s\n' "$REPOS_DIR"/* | xargs -I{} -P"$PARALLEL" bash -c 'audit_one "$@"' _ {}
# 聚合:总成本、高危项 top、失败清单
echo "===== 汇总 ====="
echo "总成本: \$$(jq -s '[.[].total_cost_usd] | add' "$REPORTS_DIR"/*.json 2>/dev/null | bc)"
echo "失败仓库: $(comm -23 <(ls "$REPOS_DIR" | sort) <(ls "$REPORTS_DIR" | sed 's/\.json$//' | sort) | tr '\n' ' ')"
echo "全局高危热点 top10:"
jq -s '[.[].result // .[] | .hotspots[]?] | flatten | .[]' "$REPORTS_DIR"/*.json 2>/dev/null | sort | uniq -c | sort -rn | head
脚本里的设计决策(每一条都对应前面某一步):
--bare 保证在 CI 容器里和本地行为一致(第一步)--json-schema 强约束输出,下游 jq 才能稳定解析(第三步)session_id 写进 state.tsv,失败重试用 --resume 续跑而非重头开始(第四步)xargs -P 控制并发,每个仓库独立写文件避免竞争(第五步)timeout 600 + 重试上限,防止卡死后台任务拖垮整条流水线(第六步)total_cost_usd,末尾用 jq -s 聚合总成本(第三步)flowchart TD
A[repos/ 下 N 个仓库] --> B{xargs -P 并发分发}
B -->|worker 1| C1[查 state.tsv 是否有 session_id]
B -->|worker 2| C2[查 state.tsv 是否有 session_id]
B -->|worker 3| C3[查 state.tsv 是否有 session_id]
C1 --> D1{有历史 session?}
C2 --> D2{有历史 session?}
C3 --> D3{有历史 session?}
D1 -->|是| E1[--resume SID 续跑]
D1 -->|否| F1[全新 --bare -p 调用]
D2 -->|是| E2[--resume SID 续跑]
D2 -->|否| F2[全新 --bare -p 调用]
D3 -->|是| E3[--resume SID 续跑]
D3 -->|否| F3[全新 --bare -p 调用]
E1 & F1 & E2 & F2 & E3 & F3 --> G[timeout 600 + --json-schema 约束]
G --> H{退出码 0?}
H -->|否, 重试≤2| G
H -->|是| I[写 reports/name.json 记 session_id 到 state.tsv]
H -->|超次失败| J[记入失败清单]
I --> K[jq -s 聚合总成本与高危热点]
J --> K
K --> L[输出汇总报告]
style B fill:#e1f5ff
style G fill:#fff3e0
style K fill:#e8f5e9
| 现象 | 原因 | 解决 |
|---|---|---|
CI 里 claude -p 一直挂着不动 |
没加 --bare,本地某个 hook/MCP 卡住了,或在等 OAuth 登录 |
加 --bare,鉴权改用 ANTHROPIC_API_KEY |
| stdin 报错"超过 10MB" | 管道喂的数据太大(v2.1.128 起 10MB 上限) | 写入文件,在 prompt 里引用路径 |
--resume <sid> 报"找不到会话" |
不在创建该会话的目录里执行 | cd 回原目录,或检查 worktree 归属 |
| 并发跑出现 429 | 撞上 API 速率限制 | 降低 -P 并发度,加重试退避 |
total_cost_usd 字段缺失 |
用了 text 输出格式 |
改 --output-format json |
后台 watch 进程让 -p 永不退出 |
旧版本无超时天花板 | 升级到 v2.1.182+,或设 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS |
| 项目 hooks 在批处理里没触发 | --bare 跳过了自动发现 |
用 --settings ./batch-settings.json 显式注入 |
claude -p 平滑升级到 Python/TypeScript SDKAutomate actions with hooks 指南:hooks 的快速上手版,配合本教程引用的 Hooks reference 全事件清单使用claude --help 与 /docs/llms.txt:本地最权威的参数清单,版本更新后第一时间核对 flag 是否有变--bare/-p/管道三件套并做透第一个管道 demo;第 2 课时讲 JSON 输出、成本追踪与 --json-schema;第 3 课时讲 session 续跑与 xargs 并发;第 4 课时学员动手实现完整 repo-audit.sh 并接入 hooks 监控jq 基础--bare 相比普通 -p 在可复现性上的本质差异--bare、--output-format、管道 stdin 10MB 上限、后台任务宽限期与 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS 的权威出处--continue/--resume/-n 命名/-p 创建会话的 resume 行为的权威说明SessionStart/Stop/Notification 三种 cadence 与 JSON input/output schema[!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 等场景全覆盖。
