---
title: "`claude -p`：单次查询模式"
description: "没有人坐在终端前的时候，用 -p 让它在一次运行里读完、做完、退出。这一节讲管道输入、可被脚本解析的输出格式，以及权限与超时该怎么设。"
updated: 2026-11-13
canonical: https://docai.cc/learn/print-mode-sdk-query/
markdown_mirror: https://docai.cc/learn/print-mode-sdk-query.md
---
## 没有人坐在终端前

定时巡检、提交前检查、合并请求里的自动评论——这些任务有一个共同点：跑的时候你不在。它们不能停在确认框上等一个永远不会来的回车。

`-p`（也可写作 `--print`）就是为这种场合准备的。它执行一轮任务，把结果写到标准输出，然后退出，不进入对话界面。

## 最小可用形态

```bash
claude -p "把 README 的英文摘要翻成中文" > summary.txt
echo $?
```

结果落在文件里，退出码留给脚本判断：成功为零，失败非零。别把两者混在一起输出，否则下游没法区分「结果为空」和「根本没有结果」。

这也是它和交互模式最本质的区别：没有下一步，只有这一次。每次调用都是独立进程，上一轮的上下文不会自动带进来。

## 从标准输入拿内容

管道是它最常被低估的用法：

```bash
git diff --staged | claude -p "总结这次改动，输出三条以内的要点"
cat error.log | claude -p "找出最可能的根因，并按可能性排序"
```

数据从标准输入进来，提示词负责说明「这是什么、要交回什么格式」。只说「分析一下」，它可能给你一篇散文；说清「输出三行，每行一个结论」，结果才能进下一道工序。

注意输入长度受上下文窗口限制。日志动辄几十兆，先 `grep`、`tail` 或按时间切片，再交给它，比整份丢进去更可靠。

## 输出得让机器读得懂

| 格式 | 形态 | 用法 |
| --- | --- | --- |
| text | 纯文本，人读 | 日志、终端展示 |
| json | 结束后一个完整对象 | 脚本取字段 |
| stream-json | 一行一个事件，持续输出 | 实时消费、接入前端 |

```bash
claude -p "检查 src/auth.ts 的安全问题，用 JSON 说明每条风险" \
  --output-format json \
  < src/auth.ts > result.json

jq -r '.result' result.json
```

字段名与结构以当前版本为准，升级后先拿一条样本验证再改脚本。要边跑边处理，就换成 `stream-json`，按行读取事件；注意 `--print` 搭配 `stream-json` 时必须同时加 `--verbose`，否则命令会直接报错退出。事件类型同样以官方文档为准，流式的机制见[流式输出](/concepts/streaming/)。

## 权限：没人按「允许」

这是 `-p` 最容易踩的坑。默认情况下，未被允许的写操作会被拒绝，模型收到拒绝结果后往往回你一段「建议这样改」。脚本看起来成功退出，文件却纹丝不动。

正确做法是把权限收窄到刚好够用：

```bash
claude -p "给 src/utils.ts 的 parseDate 补单元测试" \
  --allowedTools "Read" "Edit" "Bash(npm test *)" \
  --max-turns 8
```

只放行这个任务需要的工具，比一次性全开安全得多。`--dangerously-skip-permissions` 适合一次性容器或隔离环境，不要放进长期运行的流水线。权限判断在执行前的哪个位置，见[命令执行流程](/learn/command-execution-flow/)。

还有一个细节：放行的工具越精确，脚本越稳定。写成 `Bash(git diff *)` 这样的范围，模型不会顺手跑别的命令；只写 `Bash`，它能做的事就多得多。升级版本后回来检查一次允许列表，别让旧规则默默放宽或失效。

## 超时、重试与失败信号

无人值守时，最怕的是「卡住」和「失败了却看不出来」。两层保护就够：

```bash
timeout 120 claude -p "检查构建日志，列出会阻塞发布的错误" \
  --allowedTools "Read" "Bash(grep *)" \
  --max-turns 4 < build.log
```

外层 `timeout` 负责兜底（macOS 上可能需要 coreutils 提供的 `gtimeout`），`--max-turns` 防止它在工具之间来回打转。重试只对临时故障有意义，比如网络抖动、限流；权限被拒、输入为空这类问题，重试三次结果一样。可以这样写：

```bash
ok=0
for attempt in 1 2 3; do
  if claude -p "审查暂存区改动，只报会出错的地方" \
    --allowedTools "Read" "Bash(git diff *)" \
    --output-format json > review.json; then
    ok=1
    break
  fi
  echo "第 ${attempt} 次失败" >&2
  sleep $((attempt * 5))
done
[ "$ok" = 1 ] || { echo "三次均失败，保留日志" >&2; exit 1; }
```

判断失败不要只看输出是否为空：空输出可能是真的没有问题，也可能是进程被杀。以退出码为准，配合 `set -o pipefail`，管道里任何一段失败都能传出来。重试间隔逐次拉长的做法，见[退避重试](/concepts/retry-backoff/)。

## 放进脚本前的检查清单

- **引号。** 提示词整句包住，含反引号时用单引号。
- **轮数上限。** 用 `--max-turns` 兜底，防止来回打转。
- **输出格式。** 要完整结果用 `json`，要实时反馈用 `stream-json`。
- **失败处理。** 检查退出码，别让失败被管道吞掉。
- **时间和费用。** 给外层命令设超时，给调用频次设上限；重复度高的任务可以看看[成本估算](/learn/estimate-cost/)。
- **样本验证。** 先手动跑一条，确认输出结构，再批量。

```bash
#!/usr/bin/env bash
set -euo pipefail

report=$(claude -p "审查暂存区的改动，只列会导致故障的问题" \
  --allowedTools "Read" "Grep" "Bash(git diff *)" \
  --max-turns 6 \
  --output-format json)

echo "$report" | jq -r '.result'
```

失败时脚本会因非零退出码停下，而不是把空字符串当成「没有问题」。

`-p` 把对话变成一次函数调用：输入明确、输出可解析、成败有退出码。它和 Agent SDK 里的单次调用是同一类模式，这里练熟的参数，写进程序时概念相通。想理解每次调用消耗在哪里，可以看[Token](/concepts/token/)。下一节专门看管道这条线——[处理管道内容](/learn/process-piped-input/)。
