Chapter 05

脚本与辅助工具:scripts/ 的正确用法

让 Claude 自己写代码?还是让它调用预置脚本?本章讲清楚 Skill 的"代码边界":什么时候该固化成脚本,什么时候放手让模型现场发挥。

两种极端都不对

极端问题
全靠 Claude 现写代码相同逻辑每次重写,易出错;无法单元测试;复杂算法(如 OCR、加密)容易写错
全封装成脚本Skill 变重,维护成本高;灵活性差;用户问"能不能改个参数"要改源码

正确分界线:"确定逻辑"封装,"组合逻辑"留给 Claude

该封装为脚本
复杂算法(加密、图像处理)、依赖外部 SDK、要求精确结果(SQL 执行、数值计算)、大量 I/O(读 PDF、写 Excel)。
该让 Claude 现写
一次性的数据转换、简单的文本拼接、根据用户需求组合脚本调用、生成 SQL 查询。

脚本语言选型

语言适合场景注意
Python默认首选,生态最全(pandas / pypdf / PIL)指定 python3,避免 python 歧义
Node.js前端/Web 相关、npm 生态(puppeteer)依赖管理用 package.json,别全局装
Shell简单 git / curl / awk 组合,< 30 行超过 30 行就换 Python
Go / RustCPU 密集、冷启动敏感要预编译二进制,分发复杂
Python 为什么是默认
Claude Code 和 Skills 运行环境都预装 Python,脚本门槛最低。除非有强 Node/shell 依赖,都优先 Python。

脚本的 CLI 设计准则

1. 命令行参数用 argparseclick

# scripts/analyze.py
import argparse, json

def main():
    p = argparse.ArgumentParser()
    p.add_argument("--input", required=True)
    p.add_argument("--format", choices=["json", "csv"], default="json")
    p.add_argument("--verbose", action="store_true")
    args = p.parse_args()
    # ...

if __name__ == "__main__":
    main()

好处:python script.py --help 能输出说明,Claude 不确定用法时可以自己查。

2. 标准输出用 JSON,错误用 stderr

import sys, json

try:
    result = do_work()
    print(json.dumps({"ok": True, "data": result}))
except Exception as e:
    print(json.dumps({"ok": False, "error": str(e)}), file=sys.stderr)
    sys.exit(1)

Claude 读 Bash 结果时会看到两个流,结构化的 JSON 让它准确判断成功/失败。

3. 单职责

一个脚本做一件事。不要写"万能脚本"——让 Claude 根据任务组合调用多个小脚本。

❌ scripts/do_everything.py  (--mode=detect|fill|sign|merge)

✅ scripts/detect_fields.py
✅ scripts/fill.py
✅ scripts/sign.py
✅ scripts/merge.py

4. 幂等

同样输入跑两次结果一样。Claude 偶尔会重试脚本,幂等能避免诡异的副作用。

依赖管理

轻量方案:内嵌 requirements

my-skill/
├── SKILL.md
├── scripts/
│   └── analyze.py
└── requirements.txt     ← pip install -r 即可

Skill 激活后,Claude 读到某个 import 失败,会主动运行 pip install -r requirements.txt——前提是 SKILL.md 里说明了这个安装步骤。

完善方案:声明在 SKILL.md

## 前置依赖

首次运行前执行:
```bash
pip install -r requirements.txt
```

本 Skill 依赖:pypdf>=5.0, reportlab>=4.0。
别用 sudo pip install
Claude 执行 Bash 时可能没 sudo 权限,或者用户的 Python 是用户空间。用 pip install --user 或在 SKILL.md 建议用虚拟环境。

敏感操作的保护

Skill 里的脚本不要默认做以下事,或者默认需要用户确认:

实现方式:脚本接受 --dry-run 标志,默认打印将要做的事不执行,加 --apply 才真的跑。SKILL.md 里告诉 Claude "先 dry-run 给用户看,确认后再 apply"。

脚本的版本演进

Skill 更新时脚本可能破坏兼容性。两条原则:

  1. 脚本的 CLI 签名变更时,同步更新 SKILL.md 里的调用示例
  2. 新增参数给默认值,不要强制必填——老 Skill 调用不至于直接炸
  3. 删除脚本前,在 SKILL.md 里保留一条"旧方法,已替换为 X"的过渡说明

实战:一个完整的脚本示例

#!/usr/bin/env python3
# scripts/fetch_sec_filing.py
# 下载 SEC EDGAR 的 10-K / 10-Q 文件并抽取正文

import argparse, json, sys, re, urllib.request

UA = "SkillSample research@example.com"
EDGAR = "https://data.sec.gov"

def fetch_filing(cik: str, form: str) -> dict:
    url = f"{EDGAR}/submissions/CIK{int(cik):010d}.json"
    req = urllib.request.Request(url, headers={"User-Agent": UA})
    data = json.loads(urllib.request.urlopen(req).read())
    recent = data["filings"]["recent"]
    for i, f in enumerate(recent["form"]):
        if f == form:
            return {
                "accession": recent["accessionNumber"][i],
                "date": recent["filingDate"][i],
                "primary_doc": recent["primaryDocument"][i],
            }
    return {}

def main():
    p = argparse.ArgumentParser()
    p.add_argument("--cik", required=True)
    p.add_argument("--form", default="10-K")
    args = p.parse_args()
    try:
        info = fetch_filing(args.cik, args.form)
        if not info:
            print(json.dumps({"ok": False, "error": f"no {args.form} found"}))
            sys.exit(1)
        print(json.dumps({"ok": True, "filing": info}, indent=2))
    except Exception as e:
        print(json.dumps({"ok": False, "error": str(e)}), file=sys.stderr)
        sys.exit(2)

if __name__ == "__main__":
    main()

这个脚本符合所有准则:argparse、JSON 输出、stderr 错误、exit code 区分错误类型、User-Agent 合规、单职责。

Exit code 约定
0 成功;1 业务错误(找不到资源);2 系统错误(网络、解析异常);3+ 可自定义。Claude 看到不同 exit code 会采取不同的重试/降级策略。

本章小结