首页 › tutorials › Skill内嵌脚本:让技能自带可执行代码

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

概述

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

前置条件


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

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

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/)还是插件里,下面这行都能正确解析:

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

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

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

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

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

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

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

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


三、四步构建脚本化 Skill

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

步骤 1:建立技能骨架

操作:

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

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

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

常见问题:

步骤 2:编写 SKILL.md(frontmatter + 脚本引用)

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

---
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. 只报告,不删除。若用户要删,明确确认后再行动。

边界

**预期结果:** 保存后,新会话中输入 `/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 表示无符合条件文件。

常见问题:

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

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

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

常见问题:


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

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

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。


九、延伸阅读

训练营适用性评估

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

参考来源

  1. Extend Claude with skills — Claude Code 官方文档 — 权威来源,覆盖 frontmatter 全字段、${CLAUDE_SKILL_DIR}、动态注入、打包脚本(codebase-visualizer)与权限模型。
  2. Inside Claude Code Skills: Structure, prompts, invocation — Mikhail Shilkov — 技术博客,从内部机制角度解析 Skill 的发现、加载与调用流程。
  3. anthropics/skills — GitHub 官方仓库 — 代码仓库,提供可直接参考的脚本化 Skill 实例与目录结构惯例。
  4. Claude Code Skills: How to Set Them Up and Use Them — dev.to — 教程类内容,补充实践视角的搭建步骤与常见踩坑。
  5. Skill authoring best practices — Anthropic Platform Docs — 官方最佳实践,指导跨产品的 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学通信知识星球海报-扫码加入