本教程教你把 Claude Code 从"交互式聊天框"变成"可组合的命令行数据工具":用 -p 模式程序化调用、用 --json-schema 把输出锁死成结构化数据、用 jq 把 Claude 串进 Unix 管道,最后挂一个成本账本 hook 让批量调用可观测。适合已经会用 Claude Code 交互模式、想把它接进脚本与数据流水线的开发者、运维人和数据工程师。
claude --version 可输出版本,建议 v2.1.128 以上)jq(jq --version),本文大量依赖它解析 JSONbash 的环境(macOS / Linux / WSL)/login;若用 --bare 跑 CI,则需 ANTHROPIC_API_KEY 环境变量Unix 哲学的核心是"每个程序做好一件事,通过文本流串联"。grep、awk、jq 都是这个传统的产物。Claude Code 的 -p(print)模式让它也具备了成为管道一环的能力:读 stdin、写 stdout、退出码规范、输出可结构化。这意味着你不必把 Claude 当成一个需要手动伺候的对话框,而可以让它和 git、jq、xargs、curl 并肩工作。
举个直觉:你想给 100 篇笔记各生成一份"标题 + 摘要 + 标签"的结构化卡片,传统做法是开 100 次对话框复制粘贴。换成管道思维,就是"喂文件 → Claude 提取 → jq 落库 → 聚合成索引",整条链一行 for 循环就能跑完,而且每一步都可单独替换、可重放、可监控。
-p 调用-p(或 --print)让 Claude Code 走非交互模式:吃一条 prompt,吐一段结果,然后退出。所有交互模式下的 CLI 选项在 -p 里都可用。
# 最简单的调用
claude -p "What does the auth module do?"
# 通过 stdin 喂数据(推荐,避免转义地狱)
cat notes/2026-07-01-plan.md | claude -p "用一句话总结这篇笔记"
预期结果:终端直接打印总结文本,进程退出。-p 模式会读取 stdin,所以 cat 文件 | claude -p 提问 是最稳的喂料姿势。
关于 --bare 的取舍:默认情况下,claude -p 会加载交互式会话的全部上下文——~/.claude 里的 hooks、skills、plugins、MCP 服务器、CLAUDE.md。这在脚本里反而是隐患:同事机器上配的某个 hook 可能悄悄改变结果。加 --bare 会跳过所有自动发现,只认你显式传入的 flag:
# 可复现、零惊喜:CI 与脚本的首选
claude --bare -p "Summarize this file" --allowedTools "Read" < notes/x.md
官方明确:--bare 是脚本与 SDK 调用的推荐模式,未来会成为 -p 的默认值。但 --bare 会跳过 hooks——所以本文的"成本账本 hook"那一步,需要你在交互或非 bare 模式下才能生效。这个权衡后面会再提到。
⚠️ 注意:从 v2.1.128 起,
-p的 stdin 输入上限是 10MB。超过会以非零退出码报错。大文件请写盘后在 prompt 里引用路径,而不是cat进去。
常见问题:
--bare 会跳过 OAuth 和 keychain,认证必须来自 ANTHROPIC_API_KEY 或 --settings 里的 apiKeyHelper。--allowedTools "Read" 只放行读文件,或 --permission-mode dontAsk 走只读基线。--output-format json 拿结构化结果默认 -p 吐纯文本,适合人看,不适合程序解析。加 --output-format json 后,你会拿到一个结构化对象:
claude -p "Summarize this project" --output-format json
输出大致长这样:
{
"type": "result",
"subtype": "success",
"result": "这是一个用 FastAPI 写的待办后端……",
"session_id": "abc123de-4567-…",
"total_cost_usd": 0.0123,
"duration_ms": 4380,
"is_error": false
}
这里有几个关键字段:result 是文本结果,session_id 可用于 --resume 续跑,total_cost_usd 是本次调用的花费,duration_ms 是耗时。total_cost_usd 是批量调用做成本核算的金矿——脚本里不必再去翻用量后台。
配合 jq 提取你想要的字段:
# 只要文本结果
claude -p "Summarize this project" --output-format json | jq -r '.result'
# 只要 session id,留给后续 --resume
sid=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$sid"
预期结果:jq -r '.result' 干净地剥掉外壳,只剩纯文本;$sid 变量里是一个会话 ID,可用于多轮续跑。
常见问题:
--verbose 模式下,json 格式只在结尾输出一个对象,进度走 stderr,不会污染 stdout,管道是干净的。--json-schema 锁死输出契约(核心)让大模型自由发挥写 JSON 是灾难:它会多塞字段、漏字段、把数组写成对象、把布尔写成 "true"。--json-schema 直接把输出绑死到一个 JSON Schema 定义上,结果落在响应的 structured_output 字段里。
claude -p "Extract the main function names from auth.py" \
--output-format json \
--json-schema '{
"type": "object",
"properties": {
"functions": { "type": "array", "items": { "type": "string" } }
},
"required": ["functions"]
}'
响应里你会看到:
{
"result": "…",
"structured_output": { "functions": ["login", "verify_token", "refresh"] },
"session_id": "…",
"total_cost_usd": 0.008
}
jq 取结构化部分:
claude -p "Extract function names from auth.py" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
| jq '.structured_output.functions'
预期结果:无论模型多想啰嗦,structured_output 永远严格符合你定义的 schema——数组就是数组,必填字段一定在。这是把"AI 输出"变成"可信数据"的关键一步,也是本文与普通批量调用最大的区别。
💡 把 schema 存成文件更易维护:
--json-schema "$(cat schemas/note-card.json)",schema 进版本控制,团队共享同一份契约。
常见问题:
structured_output 字段不存在? 确认同时传了 --output-format json 和 --json-schema,二者缺一不可。有了结构化输出,Claude 就能和经典 Unix 工具平等协作。下面这条链:git diff 喂给 Claude 当 typo 检查器,结果再交给 jq 提取——Claude 只是管道中的一节。
# 把当前 diff 丢给 Claude 当拼写检查器
git diff main | claude -p \
"you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else."
# 接进管道:Claude 出结构化结果 → jq 过滤 → wc 数量
cat notes/plan.md | claude -p "提取这篇笔记涉及的 所有 技术名词,去重" \
--output-format json \
--json-schema '{"type":"object","properties":{"terms":{"type":"array","items":{"type":"string"}}},"required":["terms"]}' \
| jq '.structured_output.terms | length'
预期结果:第一条命令在终端逐条列出 diff 里的疑似拼写错误;第二条命令输出一个整数——笔记里涉及的技术名词数量。
这种"小而专"的调用比"一个大 prompt 干所有事"更稳:每节管道职责单一,坏了容易定位,换模型/换工具只动一节。
常见问题:
--allowedTools 或 --permission-mode,脚本里不能有人工确认环节。index.json把前几步拼起来:遍历 notes/*.md,每篇调一次 Claude 提取结构化卡片,最后聚合成一个站点索引。先用单篇验证:
process_one() {
local f="$1"
cat "$f" | claude -p "你是笔记编目员。读取这篇笔记,提取标题、一句话摘要、3-5个标签、涉及的核心技术。纯文本,不要编造。" \
--output-format json \
--bare \
--allowedTools "Read" \
--json-schema "$(cat schemas/note-card.json)" \
| jq -c --arg src "$f" '.structured_output + {source: $src}'
}
对应的 schemas/note-card.json:
{
"type": "object",
"properties": {
"title": { "type": "string" },
"summary": { "type": "string" },
"tags": { "type": "array", "items": { "type": "string" } },
"tech": { "type": "array", "items": { "type": "string" } }
},
"required": ["title", "summary", "tags", "tech"]
}
jq -c 把每篇压成一行 JSON(JSONL),--arg src 把文件名塞进每条记录。批量遍历并聚合成数组:
# 串行版:稳,慢
for f in notes/*.md; do process_one "$f"; done > cards.jsonl
# 聚合成一个 index.json
jq -s '{generated_at: (now | todate), count: length, notes: .}' cards.jsonl > index.json
jq -s(slurp)把多行 JSONL 收成一个数组,now | todate 给索引盖上时间戳。
并发加速:笔记多了,串行会很慢。用 xargs -P 并发(-P 4 表示同时 4 个):
find notes -name '*.md' -print0 \
| xargs -0 -n1 -P4 -I{} bash -c 'process_one "$1"' _ {} \
>> cards.jsonl
预期结果:cards.jsonl 里每行一篇笔记的结构化卡片;index.json 是一个带时间戳和总数的完整索引,可直接喂给静态站点生成器做搜索。
常见问题:
jq -c),不会跨行交错;但若并发写同一文件,务必用 >> 追加且每条单行。--bare 让 hook 不生效? 对。要触发下文的成本账本 hook,去掉 --bare(牺牲可复现性换可观测性),或在 CI 里用 --bare、本地调试用非 bare。批量跑了几百篇笔记,你肯定想知道花了多少钱、哪篇最贵。-p 的 JSON 输出里有 total_cost_usd,但那是给程序读的;如果你想自动、持续地记账,就用 hook。
Claude Code 的 hooks 配在 settings.json 里,按事件分组。我们用 Stop 事件——每次 Claude 停止响应时触发,hook 通过 stdin 收到一个 JSON,里面带 transcript_path 指向本次会话的完整记录(JSONL)。我们从中提取最后一轮的 token 用量,写进账本。
.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/cost-ledger.sh"
}
]
}
]
}
}
.claude/cost-ledger.sh:
#!/usr/bin/env bash
# 从 stdin 读 hook input,提取 transcript,算 token 成本,追加到账本
input=$(cat)
transcript=$(printf '%s' "$input" | jq -r '.transcript_path')
session_id=$(printf '%s' "$input" | jq -r '.session_id')
# 从 transcript 取最后一条带 usage 的 assistant 消息
usage=$(jq -s 'map(select(.message.usage // empty)) | last | .message.usage' "$transcript" 2>/dev/null)
[ -z "$usage" ] || [ "$usage" = "null" ] && exit 0
# 简化定价(USD / 1M tokens),按需替换为你当前模型的真实价目
in=$(printf '%s' "$usage" | jq -r '.input_tokens // 0')
out=$(printf '%s' "$usage" | jq -r '.output_tokens // 0')
cin=$(printf '%s' "$usage" | jq -r '.cache_read_input_tokens // 0')
cost=$(jq -n --argjson i "$in" --argjson o "$out" --argjson c "$cin" \
'($i*3 + $o*15 + $c*0.3)/1000000')
# 追加一行 JSONL 到账本
jq -c -n \
--arg ts "$(date -u +%FT%TZ)" \
--arg sid "$session_id" \
--argjson cost "$cost" \
--argjson in "$in" --argjson out "$out" \
'{ts:$ts, session_id:$sid, input_tokens:$in, output_tokens:$out, cost_usd:$cost}' \
>> .claude/cost-ledger.jsonl
exit 0 # exit 0 放行;exit 2 会拦截并显示 stderr
跑几篇笔记后,.claude/cost-ledger.jsonl 里会留下一条条记录:
{"ts":"2026-07-06T08:21:03Z","session_id":"abc…","input_tokens":1820,"output_tokens":240,"cost_usd":0.0088}
一键看总账:
jq -s '{runs: length, total_cost: (map(.cost_usd)|add), total_in: (map(.input_tokens)|add)}' .claude/cost-ledger.jsonl
📌 hook 退出码约定:
0成功放行;2阻断并把 stderr 展示给 Claude(可用于"输出不合规则强制重试");其他非零码是非阻塞错误(记日志但不打断)。
常见问题:
--bare 下 hook 不触发? 对,bare 模式跳过 hooks。要记账就别在那一轮用 bare;或在脚本里直接读 -p 输出的 total_cost_usd 字段自己记账(见下文实战脚本的双轨设计)。transcript_path 是绝对路径,确认进程对它有读权限。把以上拼成一个可运行的工程。目录结构:
notes-indexer/
├── notes/ # 你的 Markdown 笔记
│ ├── 2026-07-01-plan.md
│ └── 2026-07-02-rag.md
├── schemas/
│ └── note-card.json
├── .claude/
│ ├── settings.json # Stop hook 注册
│ ├── cost-ledger.sh
│ └── cost-ledger.jsonl # 自动生成
└── extract-notes.sh # 主脚本
extract-notes.sh(双轨:既写卡片,又就地记账,不依赖 hook):
#!/usr/bin/env bash
set -euo pipefail
SCHEMA="schemas/note-card.json"
OUT="cards.jsonl"
: > "$OUT" # 清空
process_one() {
local f="$1"
local resp
resp=$(cat "$f" | claude -p "你是笔记编目员。提取标题、一句话摘要、3-5个标签、核心技术。忠于原文,不要编造。" \
--output-format json \
--allowedTools "Read" \
--json-schema "$(cat "$SCHEMA")")
# 取结构化结果 + 来源文件 + 本次成本,合成一行
jq -c --arg src "$f" '
.structured_output
+ {source: $src, cost_usd: .total_cost_usd, session_id: .session_id}
' <<<"$resp"
}
# 并发处理,结果汇总
export -f process_one
export SCHEMA
find notes -name '*.md' -print0 \
| xargs -0 -n1 -P4 -I{} bash -c 'process_one "$1"' _ {} >> "$OUT" || true
# 聚合成索引
jq -s '{generated_at: (now|todate), count: length,
total_cost: (map(.cost_usd//0)|add), notes: .}' "$OUT" > index.json
echo "✓ 已生成 index.json($(jq '.count' index.json) 篇,约 \$$(jq '.total_cost' index.json))"
运行:
chmod +x extract-notes.sh
./extract-notes.sh
得到的 index.json 片段:
{
"generated_at": "2026-07-06T08:31:00Z",
"count": 2,
"total_cost": 0.0176,
"notes": [
{
"title": "七月技术规划",
"summary": "梳理本月要在后端落地的 RAG 与可观测性改造。",
"tags": ["规划", "RAG", "监控"],
"tech": ["FastAPI", "Postgres", "OpenTelemetry"],
"source": "notes/2026-07-01-plan.md",
"cost_usd": 0.0088
}
]
}
这一份 index.json 可以直接喂给任意静态站点生成器做全文搜索、标签云、技术雷达。而 .claude/cost-ledger.jsonl 在你交互式调试时默默记下每一笔开销。两条可观测路径互补:脚本内读 total_cost_usd(bare 也能用),hook 读 transcript(非 bare 才触发)。
flowchart LR
A[notes/*.md] -->|cat 管道| B[claude -p]
S[schemas/note-card.json] -->|--json-schema| B
B -->|--output-format json| C[结构化响应]
C -->|jq .structured_output| D[cards.jsonl]
C -->|.total_cost_usd| E[成本字段]
D -->|jq -s 聚合| F[index.json]
E --> F
B -.会话结束.-> H[Stop hook]
T[transcript.jsonl] --> H
H -->|算 token| L[cost-ledger.jsonl]
style B fill:#fef3c7,stroke:#b45309
style F fill:#dcfce7,stroke:#15803d
style L fill:#dbeafe,stroke:#1d4ed8
-p 一直卡着不出结果? 大概率在等工具权限确认。脚本里务必加 --allowedTools 或 --permission-mode,headless 不能有人工介入。structured_output 字段为空或缺失? 检查是否同时传了 --output-format json 与 --json-schema;schema 过于复杂时模型可能放弃,先压扁再试。--include-partial-messages 不可控时,改 --output-format json 一次性返回;② 给 prompt 加"摘要不超过 80 字"硬约束控输出长度;③ 用 cache_read_input_tokens,重复的 system prompt 走缓存便宜十倍。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',逐 token 流式打印。system/api_retry 事件(含 attempt、retry_delay_ms、error 类别),脚本可据此显示重试进度或自定义退避。CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS 可调,设 0 则无限等)。dev server 这类永不退出的后台 shell,会在结果返回后约 5 秒被回收。code.claude.com/docs/en/cli-reference——所有 flag 的权威清单,--append-system-prompt、--resume、--permission-mode 都在此。code.claude.com/docs/en/hooks-guide——从 PreToolUse、PostToolUse、UserPromptSubmit 到 Stop、SessionStart 的快速上手与示例。code.claude.com/docs/en/agent-sdk/overview——当 CLI 不够用,需要用 Python/TypeScript 做结构化输出回调、工具审批回调时,升级到 SDK。code.claude.com/docs/en/claude-code-github-actions——把本文的管道搬进 CI,PR 自动出索引。jq、JSON Schema 有基本概念。-p + --json-schema(核心难点是让学员理解"结构化输出契约"),40 分钟动手跑通批量管道,20 分钟做成本 hook 与可观测性,剩余时间做扩展练习(换 schema、接静态站点)。xargs -P → ④ 加成本 hook → ⑤ 把 index.json 接进一个现成搜索页。每一步都有可验证的产出(一个 JSON 文件),适合做闯关式训练。index.json 的 schema 合规率、cost-ledger.jsonl 的总成本、并发下的正确性作为评分维度,比"有没有跑通"更能检验对管道与契约的理解。-p/--bare/--output-format/--json-schema 权威文档,本文命令与字段均出自此处claude -p 的 jq 解析、stdin 管道、实战模式第三方详解[!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 等场景全覆盖。
