本教程面向已经能在交互式终端里用 Claude Code 写代码的工程师,目标是把"人盯着一轮轮对话"的工作方式升级为"脚本批量投递、护栏自动兜底、结果结构化回收"的流水线。我们将围绕 -p 非交互模式展开,从单一调用讲到并发编排,再用 Hooks 在 PreToolUse/PostToolUse/Stop 三个关键节点上挂载确定性脚本,最终交付一个可运行的批量代码评审示例。适用人群:DevOps 工程师、平台/SRE、需要在 CI 与批处理里驱动 Claude Code 的团队技术负责人。
claude --version 可输出版本号)。jq、git、bash 的 Linux 或 macOS 机器;Windows 用户建议在 Git Bash 或 WSL 下运行。.md/.py 文件的目录)作为演练对象。--allowedTools 与 /hooks 菜单,对权限与钩子的概念有第一印象。-p 模式与三种输出格式-p(等价 --print)是 Claude Code 的非交互开关。加上它之后,CLI 不再启动 TUI,而是接收一个提示词、执行、把结果写到 stdout 然后退出——这让它可以像 grep、jq 一样被管道串联。
# 最简单的单次调用
claude -p "auth 模块在做什么?"
# 管道喂入数据,结果重定向到文件
cat build-error.txt | claude -p '简明扼要地说明这次构建错误的根因' > output.txt
--output-format 决定了 stdout 长什么样,这是批处理里最关键的一个旋钮:
| 格式 | 用途 | 典型场景 |
|---|---|---|
text(默认) |
纯文本 | 人读、写进 changelog |
json |
结构化 JSON,含 result、session_id、total_cost_usd 与每模型成本 |
程序解析、成本归集 |
stream-json |
换行分隔的 NDJSON,逐事件流式 | 实时进度展示、长任务监控 |
注意一个容量限制:自 v2.1.128 起,管道喂入的 stdin 上限是 10MB。超出会直接报错并以非零状态码退出。批处理里若要处理超大输入,把内容写进文件、在提示词里引用路径,而不是用管道。
# 结构化输出:把项目摘要取成 JSON
claude -p "总结这个项目" --output-format json | jq -r '.result'
常见问题:调用卡住不返回。多半是触发了权限询问——-p 模式下没有人去点"允许"。解决办法见第三步的 --allowedTools,或在 CI 里用 --permission-mode。另一个坑:忘记 --bare 导致机器上 ~/.claude 里的 hooks/skills/MCP 全被加载,结果在 CI 与本地表现不一致。
--bare 锁定可重复的脚本化运行这是 v2.1.x 引入的、对批处理最重要的一个标志。--bare 跳过对 hooks、skills、plugins、MCP 服务器、自动记忆和 CLAUDE.md 的自动发现,只认你显式传进去的参数。
# 一次性摘要任务,预批准 Read,纯净环境
claude --bare -p "总结这个文件" --allowedTools "Read"
为什么批处理必须用 --bare?想象你在 CI 跑评审,队友机器上的 ~/.claude 挂了一个 PostToolUse hook 会自动 git commit。不用 --bare,这个 hook 在 CI 里也会触发,把评审产生的临时文件提交进仓库。用了 --bare,本地配置完全不参与,同一份脚本在任何机器上都产生可复现的行为。官方明确指出 --bare 是脚本与 SDK 调用的推荐模式,未来会成为 -p 的默认行为。
--bare 模式下,Claude 仍可用 Bash、文件读、文件编辑这三类基础工具。需要更多上下文时,按需用标志注入:
| 要加载的内容 | 对应标志 |
|---|---|
| 系统提示词补充 | --append-system-prompt / --append-system-prompt-file |
| 设置 | --settings <file-or-json> |
| MCP 服务器 | --mcp-config <file-or-json> |
| 自定义 Agent | --agents <json> |
| 插件 | --plugin-dir <path> / --plugin-url <url> |
认证差异:--bare 跳过 OAuth 与钥匙串读取。鉴权必须来自 ANTHROPIC_API_KEY 环境变量,或 --settings JSON 里的 apiKeyHelper。Bedrock/Vertex/Foundry 用各厂商凭证。在 CI 里务必提前把 key 注入环境,否则会以认证失败退出。
--allowedTools 与权限模式给批处理松绑批处理的头号敌人是"中途弹权限询问"。两种主流解法:
方案 A:白名单工具。 显式列出允许无询问使用的工具:
claude -p "运行测试套件并修复失败项" \
--allowedTools "Bash,Read,Edit"
--allowedTools 支持权限规则语法,可以细到子命令粒度。下面这个例子只允许四条 git 子命令:
claude -p "查看我暂存的改动并生成合适的提交信息" \
--allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"
注意 * 前的空格:Bash(git diff *) 允许任何以 git diff 开头的命令;写成 Bash(git diff*) 会连 git diff-index 一起放行。
方案 B:整会话权限模式。 不逐条列工具,而是设一个基线:
acceptEdits:允许写文件,并自动批准 mkdir/touch/mv/cp 等常见文件系统命令;其他 shell 命令仍需白名单。dontAsk:除 permissions.allow 规则与只读命令集之外一律拒绝——锁死 CI 的首选,不会越界。bypassPermissions:全部放行,仅限隔离沙箱,不要在生产仓库或能联网的环境用。# CI 里锁死模式:只跑允许的,其余直接终止
claude -p "应用 lint 自动修复" --permission-mode dontAsk
常见问题:bypassPermissions 与 --allowedTools 同时使用会产生意外行为——bypassPermissions 会盖过白名单,等于批准一切。CI 里要么用 dontAsk + 白名单(安全),要么完全隔离后用 bypassPermissions(激进),不要混搭。
批处理要把 N 个任务的结果聚合成报表,靠纯文本解析很脆。用 --output-format json 配 --json-schema 可以强制模型返回符合 schema 的对象,放进 structured_output 字段:
# 抽取 auth.py 里的函数名,强制返回字符串数组
claude -p "从 auth.py 中抽取主函数名" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
| jq '.structured_output'
返回包里除了 structured_output,还带 session_id、total_cost_usd、每模型的成本明细。批处理脚本可以把这些字段汇总,做成本归集与会话追踪:
# 取出文本结果与会话 ID,便于后续续跑
RES=$(claude -p "总结项目" --output-format json)
echo "$RES" | jq -r '.result'
echo "$RES" | jq -r '.session_id'
常见问题:schema 写得太复杂模型不遵守。建议 schema 尽量扁平、字段少、用枚举约束;对长文本字段不要用 maxLength 苛求,模型对数值约束不敏感。
批处理常遇到任务超长、中途失败需要重试的场景。--continue 和 --resume 让一次 -p 调用能接上前一次的上下文:
# 第一次:启动评审
claude -p "评审这个代码库的性能问题"
# 继续最近一次对话
claude -p "现在重点看数据库查询" --continue
claude -p "生成所有发现问题的汇总" --continue
并发跑多个会话时,靠 --resume + session ID 定位具体会话。关键点:session ID 查找的作用域是当前项目目录及其 git worktree,所以续跑命令必须与首次调用在同一目录下执行:
session_id=$(claude -p "启动评审" --output-format json | jq -r '.session_id')
claude -p "继续那次评审" --resume "$session_id"
这构成了无人值守 Agent 的基础:把长任务拆成多轮,每轮产出可检查的中间结果,失败时 --resume 续上,而不是从头再来。
把单次调用组装成批量,有两条路。
串行(顺序依赖、稳):用一个 shell 循环,配合 --continue 在同一会话里逐步推进,或每轮独立。串行的好处是成本可预测、不抢配额:
# 对目录里每个 markdown 文件做摘要,结果落盘
for f in docs/*.md; do
name=$(basename "$f" .md)
claude --bare -p "为以下文档生成一段 50 字摘要:$(cat "$f")" \
--output-format json \
| jq -r '.result' > "summaries/${name}.txt"
done
并行(独立任务、快):用 xargs -P 或 GNU parallel 控制并发度。注意 API 限流——并发太高会撞 rate limit。建议从 -P 4 起步,观察 429 后再调:
# 并发评审多个仓库,最多 4 个同时
find repos -maxdepth 1 -mindepth 1 -type d | \
xargs -I{} -P 4 sh -c '
repo=$(basename "$1")
claude --bare -p "评审 $1 的代码质量,输出三条最关键问题" \
--allowedTools "Read,Grep,Glob" \
--output-format json \
| jq -r ".result" > "reviews/${repo}.json"
' _ {}
并发编排的三个要点:
1. 每个任务独立会话:不要在并行任务间共享 --continue,会话状态会串。
2. 成本上限:用 --output-format json 回收 total_cost_usd,脚本里累加,超预算就 break。
3. 失败隔离:单个任务非零退出不应拖垮整批,用 || true 或捕获退出码单独记录。
Hooks 是 Claude Code 在生命周期节点上自动执行的脚本,是批处理"放心无人值守"的关键。批处理最常用三类事件:
| 事件 | 触发时机 | 批处理用途 |
|---|---|---|
PreToolUse |
工具调用执行前 | 拦截危险命令(rm -rf、外发请求) |
PostToolUse |
工具调用成功后 | 自动 lint、格式化、写审计日志 |
Stop |
一轮回答结束 | 跑测试、校验产出、触发下游 |
配置写在 .claude/settings.json(项目级,可提交)或 ~/.claude/settings.json(用户级)。结构是三层嵌套:事件 → matcher 组(过滤) → handler(执行的命令)。
下面是一个拦截 rm -rf 的 PreToolUse hook,matcher 用 "Bash" 精确匹配工具名,if 用权限规则语法进一步缩到 rm 子命令:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
}
]
}
]
}
}
block-rm.sh 从 stdin 读 JSON 输入,判断命令是否含 rm -rf,含则输出 deny 决策:
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "破坏性命令被 hook 拦截"
}
}'
else
exit 0 # 无决策,走正常权限流程
fi
执行流程:事件触发 → matcher 检查("Bash" 匹配工具名)→ if 检查(Bash(rm *) 匹配子命令,不匹配就连进程都不起,省开销)→ handler 运行 → Claude Code 读取决策并拦截。
matcher 语法要点:
- "*"、空或省略:匹配全部。
- 只有字母数字下划线和 |:精确匹配或 | 分隔的精确列表(Bash、Edit|Write)。
- 含其他字符:当作 JS 正则(^Notebook、mcp__memory__.*)。
- 匹配 MCP 工具用 mcp__<server>__<tool>,匹配整台服务器用 mcp__memory__.*(必须带 .*,否则被当精确字符串,一个都匹配不上)。
PostToolUse 做自动格式化是批处理高频场景,matcher 用 Edit|Write:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh" }
]
}
]
}
}
护栏 vs 权限的边界:if 过滤是 best-effort 的(无法解析的命令会 fail-open 直接放行)。要硬性阻止某类操作,用权限系统的 permissions.deny,不要指望 hook。Hook 适合"顺便做点事"(格式化、日志、软警告),权限适合"绝不允许"。
批量跑长任务时,stream-json 让你边跑边看进度、边捕获重试事件:
claude -p "解释递归" --output-format stream-json --verbose --include-partial-messages
每行是一个 JSON 事件。用 jq 过滤出文本增量,实现"打字机"效果:
claude -p "写一首关于编译原理的诗" \
--output-format stream-json --verbose --include-partial-messages \
| jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
对批处理更有价值的是两类系统事件:
system/api_retry:API 请求失败重试前发出,含 attempt、max_retries、retry_delay_ms、error_status、error(如 rate_limit/overloaded/billing_error)。批处理可以据此实现自定义退避或熔断——连撞 N 次 rate_limit 就暂停整个批次。
system/init:会话首个事件,报告已加载的 model、tools、MCP servers、plugins,以及 plugin_errors。CI 里可以检查 plugin_errors 是否非空,有插件没加载成功就让流水线失败,避免"静默降级"。
-p 模式下,如果 Claude 起了一个后台 Bash 任务(比如 dev server 或 watch build),该任务会在 Claude 返回最终结果、stdin 关闭后约 5 秒被终止。这个宽限期是为了让"刚好在结果之后完成"的任务还能交付输出。重要变化:v2.1.163 之前,一个永不退出的后台进程会把整个 claude -p 调用无限期挂住——升级到新版后这个问题消失。如果你的 CI 里曾经莫名其妙卡死,升级是解法。
下面这个完整示例把前面所有概念串起来:扫描多个仓库,每个仓库并行跑一次安全评审,用 --json-schema 强制结构化输出,用 hook 兜底拦截危险命令,最后聚合成报表。
目录结构:
batch-review/
├── repos/ # 待评审的若干仓库(子目录)
├── reviews/ # 评审结果输出
├── run-review.sh # 主编排脚本
└── .claude/
├── settings.json # hook 配置
└── hooks/
└── block-dangerous.sh
1. 护栏 hook(.claude/hooks/block-dangerous.sh,记得 chmod +x):
#!/bin/bash
# 拦截 rm -rf、curl/wcurl 外发、以及 git push 到 main
COMMAND=$(jq -r '.tool_input.command // ""')
if echo "$COMMAND" | grep -qE 'rm -rf|curl |wget |git push.*main'; then
jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"批处理护栏:禁止的危险操作"}}'
else
exit 0
fi
2. hook 配置(.claude/settings.json):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)|Bash(curl *)|Bash(wget *)|Bash(git push *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-dangerous.sh"
}
]
}
]
}
}
3. 主编排脚本(run-review.sh):
#!/bin/bash
set -u
mkdir -p reviews
SCHEMA='{"type":"object","properties":{"issues":{"type":"array","items":{"type":"object","properties":{"severity":{"type":"string","enum":["high","medium","low"]},"file":{"type":"string"},"problem":{"type":"string"}},"required":["severity","file","problem"]}}},"required":["issues"]}'
# 并发评审,最多 3 个仓库同时
find repos -maxdepth 1 -mindepth 1 -type d | \
xargs -I{} -P 3 sh -c '
repo=$(basename "$1")
echo "[$(date +%H:%M:%S)] 评审 $repo ..."
claude --bare -p "对 $1 做安全评审,找出最关键的 3 个问题,只读不要修改任何文件" \
--allowedTools "Read,Grep,Glob" \
--output-format json \
--json-schema '"'$SCHEMA'"' \
> "reviews/${repo}.json" 2> "reviews/${repo}.err"
code=$?
if [ $code -ne 0 ]; then
echo "[$(date +%H:%M:%S)] ✗ $repo 失败 (exit $code),见 reviews/${repo}.err"
else
cnt=$(jq ".structured_output.issues | length" "reviews/${repo}.json" 2>/dev/null || echo 0)
echo "[$(date +%H:%M:%S)] ✓ $repo 完成,发现 $cnt 个问题"
fi
' _ {}
# 汇总:合并所有仓库的高危问题,算总成本
echo "===== 高危问题汇总 ====="
jq -s '[.[] | .structured_output.issues[]? | select(.severity=="high")] | "共 \(length) 个高危问题"' reviews/*.json
echo "===== 本次总成本 ====="
jq -s '[.[].total_cost_usd] | add' reviews/*.json
4. 运行:
chmod +x run-review.sh .claude/hooks/block-dangerous.sh
./run-review.sh
预期输出:
[14:02:11] 评审 repo-a ...
[14:02:11] 评审 repo-b ...
[14:02:11] 评审 repo-c ...
[14:03:47] ✓ repo-b 完成,发现 3 个问题
[14:04:02] ✓ repo-a 完成,发现 2 个问题
[14:04:19] ✗ repo-c 失败 (exit 1),见 reviews/repo-c.err
===== 高危问题汇总 =====
"共 2 个高危问题"
===== 本次总成本 =====
0.1427
这个流水线体现了批量编排的全部要素:--bare 保证可复现、--allowedTools 锁死只读、--json-schema 强制结构化、hook 兜底防越界、xargs -P 控并发、jq -s 做跨文件聚合、成本字段做预算归集。把 repos/ 换成 PR 列表、issue 列表、日志文件列表,同一套骨架就能复用。
flowchart TD
A[批量任务输入<br/>repos / PRs / files] --> B[编排脚本 run-review.sh]
B --> C{xargs -P 并发分发}
C -->|任务1| D1[claude --bare -p]
C -->|任务2| D2[claude --bare -p]
C -->|任务N| DN[claude --bare -p]
D1 --> E1[生命周期 Hooks]
D2 --> E2[生命周期 Hooks]
DN --> EN[生命周期 Hooks]
subgraph Hooks护栏
E1 --> F1[PreToolUse<br/>拦截危险命令]
F1 --> G1[Claude 执行工具调用]
G1 --> H1[PostToolUse<br/>自动格式化/日志]
H1 --> I1[Stop<br/>校验产出]
end
I1 --> J1[结构化输出 JSON<br/>structured_output + session_id + cost]
J1 --> K[reviews/*.json 落盘]
K --> L[jq -s 聚合报表<br/>问题汇总 + 总成本]
L --> M{失败的任务?}
M -->|是| N[--resume 续跑]
N --> C
M -->|否| O[完成]
style F1 fill:#fdd,stroke:#c33
style H1 fill:#dfd,stroke:#393
style J1 fill:#ddf,stroke:#339
code.claude.com/docs/en/agent-sdk)。run-review.sh 嵌进 CI workflow,在 PR 上自动跑评审。type: "prompt"(单轮 LLM 判定)或 type: "agent"(带工具的子 agent 验证)做更智能的护栏。| 维度 | 评估 |
|---|---|
| 适合的训练营类型 | DevOps/SRE 进阶营、平台工程营、AI 工程化营、企业级 CI/CD 实战营 |
| 学员基础要求 | 熟练 shell 脚本与 jq,用过 Claude Code 交互模式,理解 git 与 CI 基本概念 |
| 预计教学时长 | 6–8 学时(讲授 2h + 动手实验 4h + 流水线联调 2h) |
| 实验环境 | 每人一台 Linux/macOS VM,预算约 $5–20 API 额度用于跑真实评审 |
| 难点 | 并发限流调参、hook 的 if/matcher 语法细节、--bare 与本地配置的取舍 |
| 衔接课程 | 可作为"Skill 开发"与"MCP 开发"之后的高阶自动化模块,前置建议先修交互模式与权限系统 |
-p 模式、--bare、三种输出格式、--allowedTools、权限模式与会话续跑的官方权威说明。bypassPermissions 与 allowedTools 的交互、dontAsk/acceptEdits 权限模式语义。stream-json 事件流的工程化用法。本文由微信公众号「51学通信」原创制作,站长爱卫生(微信:gprshome201101)。
[!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 等场景全覆盖。
