<!-- title: Skill内嵌脚本：让技能自带可执行代码 -->

# Claude Code Skill 内嵌脚本：让技能自带可执行代码

## 概述

本教程聚焦 Claude Code Skill 的一个进阶能力：**把可执行脚本打包进技能目录，让 Claude 在需要时自动调用**。多数人把 Skill 当成"带 frontmatter 的提示词"，但当任务涉及扫描目录、解析数据、生成图表、跑校验逻辑时，纯提示往往力不从心。本教程教你区分"加载的文档"与"执行的脚本"，掌握 `${CLAUDE_SKILL_DIR}` 路径解析、动态注入与脚本执行的边界，并从零构建一个可运行的大文件雷达技能。适合已写过基础 Skill、想把技能从"动嘴"升级到"动手"的开发者。

## 前置条件

- 已安装 Claude Code（v2.1.145 或更高，以使用 `${CLAUDE_SKILL_DIR}`、`/skills` 菜单等较新特性）
- 写过至少一个只含 `SKILL.md` 的基础技能
- 本机有 Python 3（实战案例脚本仅用标准库，无需额外依赖）
- 一个可试验的目录（任何代码仓库或文件夹均可）

---

## 一、为什么 Skill 需要内嵌脚本

一个 Skill 本质上是一个文件夹，入口是 `SKILL.md`。Anthropic 官方文档给出的标准结构是这样的：

```text
my-skill/
├── SKILL.md          # 主指令（必需）
├── template.md       # 模板，供 Claude 填充
├── examples/
│   └── sample.md     # 示例输出
└── scripts/
    └── validate.sh   # 脚本，可被 Claude 执行
```

关键在于最后一行注释——脚本是 **"executed, not loaded"（被执行，而非被加载）**。这一句话是理解本教程全部内容的钥匙。同一个文件夹里，文件可以承担三种完全不同的角色，对应三种不同的"消费方式"：

| 角色 | 典型文件 | Claude 如何使用 | 进入上下文的时机 |
|:---|:---|:---|:---|
| 主指令 | `SKILL.md` | 作为运行时指令阅读 | 被触发时整体加载 |
| 参考资料 | `reference.md`、`examples/` | 用 `Read` 工具按需读取 | 需要时才读，可省 token |
| 可执行脚本 | `scripts/*.py`、`*.sh` | 用 `Bash` 工具运行 | 永不进入文本上下文，只取其输出 |

纯提示型 Skill 能力有上限：让 Claude "逐行统计目录大小"既慢又不可靠；让它"按规则校验一份 JSON"很容易出错。而把这些确定性的、可验证的工作交给一段打包好的脚本，Claude 只负责判断何时调用、如何解读结果，分工立刻清晰。这就是脚本化 Skill 的核心价值——**把确定性的计算交给代码，把判断性的决策交给模型**。

---

## 二、两个关键机制，务必分清

在动手之前，必须厘清两个容易混淆的机制。搞混它们是初学者最常踩的坑。

### 机制 A：`${CLAUDE_SKILL_DIR}`——脚本路径的"绝对锚点"

当 Claude 把 SKILL.md 里的 bash 代码块交给 `Bash` 工具执行时，它并不知道脚本文件在哪。直接写相对路径 `scripts/foo.py` 会因为当前工作目录（CWD）不同而失败。官方提供了专门的字符串替换变量 `${CLAUDE_SKILL_DIR}`，它在 Skill 内容进入上下文前就会被替换成"存放该 SKILL.md 的那个目录"，**与当前工作目录无关**。

这意味着无论这个技能被装在个人目录（`~/.claude/skills/`）、项目目录（`.claude/skills/`）还是插件里，下面这行都能正确解析：

```bash
python3 ${CLAUDE_SKILL_DIR}/scripts/big_file_radar.py .
```

对插件型技能，该变量指向插件内技能的子目录，而非插件根目录，行为同样可预测。

### 机制 B：`!` 动态注入——Claude Code 自己跑的预处理

这是另一个常被混为一谈的能力。在 SKILL.md 中，行首或空白后紧跟的 `` !`命令` `` 语法会让 **Claude Code（宿主程序）在把内容发给模型之前先执行该命令**，并把输出原地替换进去。模型看到的是结果，不是命令本身。

```markdown
## 当前变更
!`git diff HEAD`
```

它的典型用途是"把实时数据带进提示"，而不是"让 Claude 主动跑脚本"。两者区别如下：

| 维度 | `!` 动态注入 | 内嵌脚本执行 |
|:---|:---|:---|
| 谁来执行 | Claude Code 宿主（预处理） | Claude 通过 `Bash` 工具（运行时决策） |
| 执行时机 | Skill 加载前，一次性 | Claude 判断需要时，可多次、可带不同参数 |
| 模型是否"知道" | 不知道，只看到结果 | 知道，由模型主动发起 |
| 受 `allowed-tools` 控制 | 否（受 `disableSkillShellExecution` 策略控制） | 是，按 Bash 权限规则放行 |
| 适合的场景 | 喂入固定的实时上下文（diff、PR 信息） | 需要按用户参数动态调用、需多次执行的逻辑 |

一句话总结：**`!` 适合"每次都先把环境信息搬进来"，内嵌脚本适合"按需执行一段真正的计算"**。本教程聚焦后者，但两者经常组合使用——用 `!` 注入环境概况，再用内嵌脚本做精细处理。

---

## 三、四步构建脚本化 Skill

下面用一个全新原创案例贯穿全程：**big-file-radar（大文件雷达）**——一个打包 Python 脚本的技能，能扫描任意目录、找出占用空间最大的若干文件、输出排名表与可选 CSV。

### 步骤 1：建立技能骨架

**操作：**

```bash
mkdir -p ~/.claude/skills/big-file-radar/scripts
```

这里把它放在个人目录，于是所有项目都能用。若只想让某个项目用，改成项目内的 `.claude/skills/big-file-radar/scripts` 即可。

**预期结果：** 目录创建成功。Claude Code 会实时侦测 `SKILL.md` 的文本变更，但首次新建一个会话开始时还不存在的顶级 skills 目录时，需要重启一次 Claude Code 才能纳入侦听。

**常见问题：**

- *我在会话中创建了目录但 `/skills` 里看不到*：新建原本不存在的顶级 skills 目录需重启 Claude Code；之后对该目录下文件的增删改即可热生效，无需重启。
- *目录名能用中文吗*：技术上可以，但命令名取自目录名，中文会变成 `/中文技能`，输入不便，建议用 kebab-case 英文名。

### 步骤 2：编写 SKILL.md（frontmatter + 脚本引用）

**操作：** 在 `~/.claude/skills/big-file-radar/SKILL.md` 写入以下内容。注意三处关键设计：`allowed-tools` 预批脚本执行权、`argument-hint` 给出参数提示、正文用 `${CLAUDE_SKILL_DIR}` 锚定脚本。

```yaml
---
name: big-file-radar
description: 扫描目录找出最大的文件并生成排名表与可选CSV。当用户想清理磁盘、定位大文件、分析仓库体积、找出占空间的文件时使用。
allowed-tools: Bash(python3 *)
argument-hint: "[目录] [--top N] [--min-size 字节] [--csv out.csv]"
---

# 大文件雷达

当用户想找出占用空间最多的文件时，运行内置脚本扫描目录并生成 Markdown 排名表。

## 用法

从目标目录运行（默认扫描当前目录，`$ARGUMENTS` 会被替换为用户传入的参数）：

```bash
python3 ${CLAUDE_SKILL_DIR}/scripts/big_file_radar.py $ARGUMENTS
```

脚本会自动跳过 .git、node_modules、__pycache__、.venv、dist、build 等目录，输出一张 Markdown 表格（Rank / Size / Path）。

## 你的任务

1. 把用户自然语言翻译成脚本参数（例如"找出最大的 20 个" → `--top 20`；"只看大于 10MB 的" → `--min-size 10485760`），运行上面的命令。
2. 读取脚本输出的排名表，向用户总结：哪些文件最大、是否值得清理、是否有异常（如几十 GB 的日志或 dump）。
3. 若用户要导出，加 `--csv report.csv` 重新运行。
4. **只报告，不删除**。若用户要删，明确确认后再行动。

## 边界
- 脚本只读，不修改任何业务文件（CSV 仅在用户显式指定路径时生成）。
- 退出码 2 表示无符合条件的文件，属正常情况，不报错。
```

**预期结果：** 保存后，新会话中输入 `/big-file-radar` 即可手动触发；或直接说"这个仓库里最大的文件是哪些"，Claude 会因 `description` 匹配而自动加载。

**常见问题：**

- *`allowed-tools: Bash(python3 *)` 是不是限制 Claude 只能跑 python3*：不是白名单。它的真实含义是"本技能激活期间，允许免确认地运行匹配 `python3 *` 的 Bash 命令"。其它工具依然可用，权限由全局权限规则继续约束。切勿把含脚本的技能签入他人不受信任的仓库，因为它能给自己授予较宽的工具权限——首次使用项目技能时需先通过工作区信任对话框。
- *`description` 被截断了*：技能描述与会话内 token 预算挂钩（约为模型上下文窗口的 1%），技能很多时低频技能的描述会被优先裁剪。把最关键的触发词放在 `description` 开头；可用 `/doctor` 查看哪些描述被裁剪，必要时调高 `skillListingBudgetFraction` 或把冷门技能设为 `name-only`。

### 步骤 3：编写可移植的打包脚本

**操作：** 在 `~/.claude/skills/big-file-radar/scripts/big_file_radar.py` 写入脚本。设计要点：只用标准库、带 `argparse`、有清晰的退出码、把非关键信息写 `stderr`（不污染数据输出）。

```python
#!/usr/bin/env python3
"""Scan a directory and report the largest files above a threshold."""
import argparse, csv, os, sys

def human_size(n):
    for unit in ("B", "KB", "MB", "GB"):
        if n < 1024:
            return f"{n:.1f} {unit}"
        n /= 1024
    return f"{n:.1f} TB"

def scan(root, ignore):
    results = []
    for dirpath, dirnames, filenames in os.walk(root):
        # prune ignored dirs in-place for speed
        dirnames[:] = [d for d in dirnames if d not in ignore and not d.startswith('.')]
        for f in filenames:
            p = os.path.join(dirpath, f)
            try:
                results.append((os.path.getsize(p), p))
            except OSError:
                continue
    return sorted(results, reverse=True)

def main():
    ap = argparse.ArgumentParser(description="Find largest files in a directory tree")
    ap.add_argument("root", nargs="?", default=".", help="directory to scan")
    ap.add_argument("--top", type=int, default=15, help="number of rows to show")
    ap.add_argument("--min-size", type=int, default=1024, help="minimum size in bytes")
    ap.add_argument("--csv", default=None, help="optional CSV output path")
    args = ap.parse_args()

    ignore = {".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build"}
    rows = [r for r in scan(args.root, ignore) if r[0] >= args.min_size]
    top = rows[:args.top]

    print(f"# Top {len(top)} files >= {human_size(args.min_size)} under {args.root}\n")
    print("| Rank | Size | Path |")
    print("|---:|---:|:---|")
    for i, (size, path) in enumerate(top, 1):
        print(f"| {i} | {human_size(size)} | `{path}` |")

    if args.csv:
        with open(args.csv, "w", newline="") as fh:
            w = csv.writer(fh)
            w.writerow(["rank", "size_bytes", "size_human", "path"])
            for i, (size, path) in enumerate(top, 1):
                w.writerow([i, size, human_size(size), path])
        print(f"CSV written to {args.csv}", file=sys.stderr)

    sys.exit(0 if top else 2)

if __name__ == "__main__":
    main()
```

**预期结果：** 在终端直接跑 `python3 ~/.claude/skills/big-file-radar/scripts/big_file_radar.py . --top 5`，应立即输出一张排名表。退出码 0 表示有结果，2 表示无符合条件文件。

**常见问题：**

- *Claude 跑脚本时报 `Permission denied`*：脚本需要可执行权限。若你打算用 `./big_file_radar.py` 直接调用，先 `chmod +x`；但本例用 `python3 xxx.py` 调用，则不需要可执行位，更稳妥跨平台。
- *脚本依赖第三方库怎么办*：尽量只用标准库以保可移植。若必须用第三方库，可在脚本里用 `try/except ImportError` 给出友好提示，并在 SKILL.md 注明前置安装步骤；或者把依赖固化进一个虚拟环境并在 SKILL.md 里指定解释器绝对路径。
- *输出里混进了进度信息导致表格错乱*：进度、提示、调试信息一律写 `sys.stderr`（如 `print(..., file=sys.stderr)`），只有正式数据走 `stdout`，这样 Claude 解析表格时不会被干扰。

### 步骤 4：让 Claude 知道脚本何时、如何运行

**操作：** 这一步其实已融入步骤 2 的 SKILL.md——靠正文里的"用法""你的任务""边界"三段。关键写法是：明确给出可复制的命令模板、把自然语言到参数的翻译规则讲清楚、划清"只读/不破坏"的安全边界。

**预期结果：** 在任意目录启动 Claude Code，说"看看这个项目哪些文件最大"。Claude 会自动加载技能、调用脚本、解读结果并给出清理建议；也可手动 `/big-file-radar . --top 20 --csv top.csv`。

**常见问题：**

- *技能不触发*：先用 `What skills are available?` 确认它出现在列表里；再检查 `description` 是否含用户会说的关键词（"最大文件""占空间""磁盘"）。若 YAML 解析失败，Claude Code 会以空元数据加载，`/big-file-radar` 仍可手动调用但无法自动触发——用 `--debug` 启动可看到解析错误。
- *技能触发但 Claude 没跑脚本*：检查 `allowed-tools` 是否匹配实际调用方式。如果脚本写成 `./big_file_radar.py`，则 `allowed-tools: Bash(python3 *)` 不匹配，会被权限拦下。两者必须对齐。
- *脚本被调用但参数没传进去*：确认正文里有 `$ARGUMENTS`。若正文完全没有 `$ARGUMENTS`，Claude Code 会把参数以 `ARGUMENTS:` 形式追加到末尾，模型不一定按预期解析。

---

## 四、脚本化 Skill 的完整工作流

下面的流程图展示了一次调用从用户输入到脚本执行、再到结果解读的全过程，以及动态注入与内嵌脚本两种机制各自介入的位置：

```mermaid
flowchart TD
    A[用户消息: 这个仓库哪些文件最大] --> B{description 是否匹配}
    B -- 否 --> C[不加载技能, 走默认流程]
    B -- 是 --> D[Claude Code 加载 SKILL.md]
    D --> E{内容里有 !反引号 注入?}
    E -- 是 --> F[宿主预处理: 运行命令并替换输出]
    E -- 否 --> G[直接交给模型]
    F --> G
    G --> H[模型读取'用法'中的命令模板]
    H --> I[模型发起 Bash 工具调用]
    I --> J["Bash: python3 $&#123;CLAUDE_SKILL_DIR&#125;/scripts/...py $ARGUMENTS"]
    J --> K["变量替换: CLAUDE_SKILL_DIR -> 技能目录"]
    K --> L{allowed-tools 是否放行}
    L -- 否 --> M[弹权限确认]
    L -- 是 --> N[执行脚本]
    M --> N
    N --> O[stdout: 排名表 / stderr: 提示]
    O --> P[模型解读结果并总结建议]
    P --> Q[回复用户]
```

读懂这张图，就抓住了脚本化 Skill 的运行本质：**SKILL.md 是说明书，脚本干重活，`${CLAUDE_SKILL_DIR}` 保证路径稳，`allowed-tools` 控制权限闸**。

---

## 五、frontmatter 字段速查（脚本化 Skill 视角）

并非所有字段都和脚本执行相关，下面这张表只挑出最相关的几项：

| 字段 | 对脚本化 Skill 的作用 | 示例 |
|:---|:---|:---|
| `description` | 决定模型何时自动调用——写得准，脚本才会被用上 | "找出最大的文件并生成排名表" |
| `allowed-tools` | 预批脚本执行权，免去每次确认 | `Bash(python3 *)` |
| `argument-hint` | 自动补全时提示参数形态 | `[目录] [--top N]` |
| `arguments` | 声明命名位置参数，正文可用 `$name` 引用 | `arguments: [dir, n]` |
| `disable-model-invocation` | 仅手动 `/name` 触发，适合有副作用的脚本 | `true` |
| `model` | 让该技能在更便宜/更强的模型上运行 | `haiku` 或 `inherit` |
| `context` | 设为 `fork` 在隔离子代理中跑，隔离上下文 | `fork` |

一个值得记住的设计原则：**纯只读、低风险的脚本型技能保持自动触发（默认）；会写文件、发请求、删数据的，务必加 `disable-model-invocation: true`**，由人按下按钮再跑。

---

## 六、实战演练：从零到可用

把前面四步串起来，做一次完整验证：

1. 创建骨架与两个文件（步骤 1–3）。
2. 在终端单独跑通脚本，确认退出码与输出格式正确。
3. 启动 Claude Code，先问"What skills are available?"确认 `big-file-radar` 已被发现。
4. 用自然语言测试："帮我看一下 `~/projects/my-app` 里最大的 10 个文件"。
5. 用命令式测试：`/big-file-radar ~/projects/my-app --top 10 --csv big.csv`。
6. 故意制造边界：指向一个几乎空的目录，确认退出码 2 被正确识别为"无结果"而非报错。

若六步都通过，这个技能就具备了生产可用性。它还可继续演化：加 `--since` 按修改时间过滤、加 `--ext` 按扩展名过滤、把输出从 Markdown 换成交互式 HTML（官方 `codebase-visualizer` 示例正是用打包脚本生成可视化 HTML 的同款套路）。

---

## 七、调试与排错清单

脚本化 Skill 出问题时，按以下顺序排查，命中率最高：

1. **脚本本身能跑吗**：先脱离 Claude，在终端用相同参数直接执行脚本。脚本层 bug 占问题的半数以上。
2. **路径对吗**：在 SKILL.md 里 `echo ${CLAUDE_SKILL_DIR}` 或临时打印确认替换结果。务必用 `${CLAUDE_SKILL_DIR}` 而非相对路径或硬编码绝对路径。
3. **权限放行了吗**：`allowed-tools` 的模式要与实际命令匹配。`Bash(python3 *)` 不匹配 `python script.py`（无 `python3`）也不匹配 `./script.py`。
4. **触发了吗**：`/doctor` 看描述是否被裁剪；用 `/skill-name` 手动调用可绕过自动触发问题。
5. **YAML 合法吗**：frontmatter 解析失败会静默退化为空元数据，`--debug` 启动能看到错误。
6. **输出干净吗**：所有非数据输出走 `stderr`，否则模型解析表格式数据时会错乱。
7. **会话还在用旧版本吗**：SKILL.md 文本变更热生效，但如果脚本文件在会话开始后才新建且触发了目录侦听重启逻辑，重启一次最稳妥。

---

## 八、性能、成本与安全考量

把脚本打包进技能，收益与代价并存，需要在三个维度上权衡。

**Token 成本**：SKILL.md 一旦触发就会在后续轮次常驻上下文，每一行都是反复计费的 token。官方建议把 SKILL.md 控制在 500 行以内，把详尽的参考资料移到独立文件按需 `Read`。脚本本身不占 token（它不进文本上下文，只有输出进入），所以**重逻辑应放进脚本而非写成长提示**——这恰恰是脚本化 Skill 的另一个隐性优势：用代码体积换 token 体积。

**可观测性**：脚本带退出码、走 stdout/stderr，比"让模型在脑子里算"可调试得多。建议脚本输出结构化、可解析的结果（表格、JSON），让模型的解读步骤也变得更可靠。可用 `skill-creator` 插件（`/plugin install skill-creator@claude-plugins-official`）做 A/B 评测：同一批测试用例，对比"有技能/无技能"的通过率与 token 开销，量化脚本化改造的收益。

**安全**：`allowed-tools` 是"授予"，不是"限制"。一个被签入仓库的技能可以为自己授予较宽的执行权限，所以**不要在不受信任的仓库里盲目通过工作区信任对话框**。对只读型技能，脚本里应显式只做读取操作；对会修改数据的技能，用 `disable-model-invocation: true` 强制人工触发，并在脚本里对破坏性操作加二次确认。若组织需要全局禁用技能内的 shell 注入，可在托管设置里设 `"disableSkillShellExecution": true`。

---

## 九、延伸阅读

- **Agent Skills 开放标准**（agentskills.io）：Skill 的跨工具通用规范，Claude Code 在此基础上扩展。
- **Claude Code 官方文档 · Skills**（code.claude.com/docs/en/skills）：frontmatter 全字段、动态注入、子代理执行等权威说明。
- **anthropics/skills 仓库**（github.com/anthropics/skills）：官方维护的 Skills 公共仓库，含可直接参考的脚本化技能实例。
- **skill-creator 插件**：技能评测与迭代工具链，支持 benchmark、版本盲测、描述调优。
- **官方 codebase-visualizer 示例**：与本教程的大文件雷达同属"打包脚本生成产物"模式，可对照学习如何用脚本生成交互式 HTML。

## 训练营适用性评估

本教程适合 **"AI 工程化 / Agent 开发进阶"** 类训练营，定位为"基础 Skill 之后的第一节进阶课"。前置要求学员已能独立写出纯提示型 Skill，因此不建议作为零基础入门内容。建议教学时长 **90–120 分钟**：30 分钟讲授三种内容模式的边界与两大机制，45 分钟动手搭建大文件雷达并完成六步验证，剩余时间用于排错演练与安全讨论。若训练营偏运维方向，可把案例替换为"日志 Top-N 错误统计"或"配置文件合规检查"，骨架与方法完全通用。产出验收标准：学员能独立交付一个含打包脚本、带正确 `${CLAUDE_SKILL_DIR}` 与 `allowed-tools`、通过 `skill-creator` 基础评测的只读型技能。

## 参考来源

1. [Extend Claude with skills — Claude Code 官方文档](https://code.claude.com/docs/en/skills) — 权威来源，覆盖 frontmatter 全字段、`${CLAUDE_SKILL_DIR}`、动态注入、打包脚本（codebase-visualizer）与权限模型。
2. [Inside Claude Code Skills: Structure, prompts, invocation — Mikhail Shilkov](https://mikhail.io/2025/10/claude-code-skills/) — 技术博客，从内部机制角度解析 Skill 的发现、加载与调用流程。
3. [anthropics/skills — GitHub 官方仓库](https://github.com/anthropics/skills) — 代码仓库，提供可直接参考的脚本化 Skill 实例与目录结构惯例。
4. [Claude Code Skills: How to Set Them Up and Use Them — dev.to](https://dev.to/padawanabhi/claude-code-skills-how-to-set-them-up-and-use-them-1chm) — 教程类内容，补充实践视角的搭建步骤与常见踩坑。
5. [Skill authoring best practices — Anthropic Platform Docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — 官方最佳实践，指导跨产品的 Skill 编写与精简原则。

> [!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 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
