跳到正文
docai.cc

DOCAI · 课程 · L3 · 21 分钟 · 命令参考

脚本编写

把每周重复的评审、整理、批处理写成 shell 脚本:四段式骨架、用退出码和结构化输出做判断、限流重试,以及权限与成本的边界。

Claude Code 程序员 · 命令组合与集成自动化

你将学会

  • 能把一次性的手动流程改写成可重复执行的脚本
  • 能用退出码与结构化输出让脚本自己做判断
  • 能给批处理加上限流、重试和成本记录

先问值不值得固化

每周整理周报,开对话、贴 diff、复制结果,十分钟。做到第四次,就该考虑写成脚本。判断标准三条:步骤固定、输入能列举、结果有客观验收方式。缺一条都先别急。

只做过一次的任务,手动跑通再说。你会看到它在哪里出岔子,哪些判断必须由人来做。反过来,一周只跑一次、每次输入都不同的活,写脚本的维护成本高于收益。

脚本只放大流程,不替你定规则。把模型看成一个工位:前面有人备料,后面有人验收。它擅长分类、提取、改写、判断,不擅长替你拍板。

四段式骨架

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

input="${1:?用法: review.sh <文件>}"
[ -f "$input" ] || { echo "文件不存在: $input" >&2; exit 2; }

draft=$(claude -p "审查这段代码的并发问题,按严重程度排序,每条给出位置和建议" < "$input")
printf '%s\n' "$draft" > "${input%.*}.review.md"

四段:校验参数、取输入、交给模型、保存结果。每段都短,坏了容易定位。别把全部逻辑压进一条管道,出错时你连哪一步坏的都说不清。

输入最好用绝对路径,或者先切到固定目录。脚本被定时任务调用时,工作目录往往不是你以为的那个。

让脚本自己判断

模型输出一段散文,脚本没法据此分支。两个办法:

  1. 约定结构。要求「只输出 JSON」或「只输出一行 PASS/FAIL」,脚本解析字段,失败按失败处理。
  2. 看退出码。调用成功返回零,失败返回非零,先判断这一步再决定是否继续。
if ! out=$(claude -p "只输出 PASS 或 FAIL,后附一句理由" < "$file"); then
  echo "调用失败:$file" >&2
  exit 1
fi

模型说 FAIL,也别让脚本直接删文件。脚本负责发现,人负责处置。

解析 JSON 失败时不要猜。把原始输出和错误一起记进日志,下一次改提示词才有依据。

用 JSON 契约代替「看着像对」

散文输出没法分支,最稳的办法是让模型只交固定字段,脚本再校验。下面这段可以整块抄进循环:

for f in docs/*.md; do
  out=$(claude -p '读取标准输入的内容,只输出一行 JSON,不要解释。字段:{"level":"high|medium|low","reason":"不超过20字"}。第一个字符是左花括号,最后一个字符是右花括号,不要代码块标记。' < "$f")

  if ! printf '%s' "$out" | jq -e '.level and .reason' >/dev/null 2>&1; then
    printf '格式不符: %s\n' "$f" >> build/failures.log
    continue
  fi

  printf '%s\n' "$out" > "build/$(basename "${f%.md}").json"
done

这里把正文走标准输入,提示里只写规则,两边不会混。jq -e 在字段缺失时返回非零,正好当校验开关。字段名、取值范围、失败时返回什么,都要在提示里写死;写「尽量输出 JSON」等于没写。

提示词里还要防一种情况:模型在 JSON 前后加解释。明确写「第一个字符是左花括号,最后一个字符是右花括号,不要代码块标记」。解析仍然失败就记日志,不要用正则去猜——猜出来的结果会把错误藏得更深。

批量处理的底线

  • 逐条记账:文件名、耗时、输出路径,失败时知道断在哪。
  • 不覆盖原件:写到新目录或新文件,保留可回滚的版本。
  • 先小后大:先跑十条,确认输出形状和失败率,再放全量。
for f in docs/*.md; do
  out="build/$(basename "${f%.md}").summary.md"
  mkdir -p "$(dirname "$out")"
  claude -p "用三句话概括这篇文档,输出纯文本" < "$f" > "$out" \
    || echo "失败: $f" >> build/failures.log
done

先拿一条样本彩排。把写操作换成打印,确认输入取对了、提示词给出的形状符合预期、输出路径没写错。通配符没匹配到文件、变量为空、目录不存在,这些错误在单条命令里一眼可见,进了循环就会被淹没。

重试与预算

把上一节循环里的调用换成带重试的函数,外层遍历不用改:

prompt='用三句话概括这篇文档,输出纯文本'

run_with_retry() {
  local f="$1" out="$2" attempt
  for attempt in 1 2 3; do
    if claude -p "$prompt" < "$f" > "$out.part"; then
      mv "$out.part" "$out"
      return 0
    fi
    if [ "$attempt" -lt 3 ]; then
      sleep $((2 ** (attempt - 1)))
    fi
  done
  rm -f "$out.part"
  printf '放弃(3 次): %s\n' "$f" >> build/failures.log
  return 1
}

调用时把上一节的 claude -p ... > "$out" 换成 run_with_retry "$f" "$out";失败已经写进日志,外面不用再记一次。等待按 1 秒、2 秒递增,第三次失败就记账走人。只重试可能恢复的错误:网络抖动、限流、超时值得再试;文件不存在、提示词写错、权限不足,重试多少次都一样。日志里记下尝试次数,才能区分偶发失败和「这段输入根本处理不了」。

预算按两笔算:调用次数和 token。开跑前给这一批定一条上限,比如最多 200 次调用、总 token 不超过某个数;跑到一半核对日志,超了就停下来看失败原因,而不是让它继续跑。每次调用把输入长度、输出长度记进同一条日志,月底才知道钱花在哪。限流机制见速率限制,退避策略见重试与退避。

把一次性的手动流程固化成脚本,和自动化重复任务是同一件事的两个阶段:先手动跑通,再交给脚本。

权限、成本与提示词版本

无人值守时,最大的风险不是答错,是它做了不该做的动作。先划定:读什么、写什么、能不能联网。范围越小,越敢让它自己跑。默认按只读设计;要写,就限定在产物目录;要跳过确认,先确保环境隔离。别在生产仓库里图省事。

成本按调用次数、输入和输出长度走。跑批量前用一条样本估算总量,日志里记下每次调用的元数据。能先筛掉无关内容,就别整份塞进去,token 怎么算见 Token。

脚本里的提示词是核心逻辑,不是随手字符串。抽到单独文件,和代码一起提交;改动记原因,用固定样本回测。出问题能回滚,也能看出改动让结果变好还是变坏。权限设置也写进脚本说明:谁在什么环境下能跑、跳过确认的前提是什么。

什么时候别写脚本

需要人判断取舍、规则还没稳定、只做一次的流程,都不适合。脚本会把错误放大:手动跑错一次立刻发现,脚本跑错可能已经处理了几百个文件。

下一节把终端操作练顺,减少写脚本时的摩擦:键盘快捷键。

自测

先自己回答,再展开对照;答不上来就回到对应小节。
  1. 01什么样的任务值得写成脚本?看答案

    步骤固定、输入可以列举、结果有客观验收方式。三项都满足才固化;只做过一次的流程先手动跑通。

  2. 02脚本里为什么不能只解析模型输出的文字?看答案

    文字格式不稳定,失败时也可能返回一段话。要看退出码,并要求结构化输出,解析失败就按失败处理。

查看 Markdown

涉及的概念

在知识树上