本教程聚焦 Claude Code Skill 的一个进阶能力:把可执行脚本打包进技能目录,让 Claude 在需要时自动调用。多数人把 Skill 当成"带 frontmatter 的提示词",但当任务涉及扫描目录、解析数据、生成图表、跑校验逻辑时,纯提示往往力不从心。本教程教你区分"加载的文档"与"执行的脚本",掌握 ${CLAUDE_SKILL_DIR} 路径解析、动态注入与脚本执行的边界,并从零构建一个可运行的大文件雷达技能。适合已写过基础 Skill、想把技能从"动嘴"升级到"动手"的开发者。
${CLAUDE_SKILL_DIR}、/skills 菜单等较新特性)SKILL.md 的基础技能一个 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 的核心价值——把确定性的计算交给代码,把判断性的决策交给模型。
在动手之前,必须厘清两个容易混淆的机制。搞混它们是初学者最常踩的坑。
${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 .
对插件型技能,该变量指向插件内技能的子目录,而非插件根目录,行为同样可预测。
! 动态注入——Claude Code 自己跑的预处理这是另一个常被混为一谈的能力。在 SKILL.md 中,行首或空白后紧跟的 !`命令` 语法会让 Claude Code(宿主程序)在把内容发给模型之前先执行该命令,并把输出原地替换进去。模型看到的是结果,不是命令本身。
## 当前变更
!`git diff HEAD`
它的典型用途是"把实时数据带进提示",而不是"让 Claude 主动跑脚本"。两者区别如下:
| 维度 | ! 动态注入 |
内嵌脚本执行 |
|---|---|---|
| 谁来执行 | Claude Code 宿主(预处理) | Claude 通过 Bash 工具(运行时决策) |
| 执行时机 | Skill 加载前,一次性 | Claude 判断需要时,可多次、可带不同参数 |
| 模型是否"知道" | 不知道,只看到结果 | 知道,由模型主动发起 |
受 allowed-tools 控制 |
否(受 disableSkillShellExecution 策略控制) |
是,按 Bash 权限规则放行 |
| 适合的场景 | 喂入固定的实时上下文(diff、PR 信息) | 需要按用户参数动态调用、需多次执行的逻辑 |
一句话总结:! 适合"每次都先把环境信息搬进来",内嵌脚本适合"按需执行一段真正的计算"。本教程聚焦后者,但两者经常组合使用——用 ! 注入环境概况,再用内嵌脚本做精细处理。
下面用一个全新原创案例贯穿全程:big-file-radar(大文件雷达)——一个打包 Python 脚本的技能,能扫描任意目录、找出占用空间最大的若干文件、输出排名表与可选 CSV。
操作:
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 英文名。操作: 在 ~/.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)。
--top 20;"只看大于 10MB 的" → --min-size 10485760),运行上面的命令。--csv report.csv 重新运行。**预期结果:** 保存后,新会话中输入 `/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 表示无符合条件文件。
常见问题:
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 解析表格时不会被干扰。操作: 这一步其实已融入步骤 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 启动可看到解析错误。allowed-tools 是否匹配实际调用方式。如果脚本写成 ./big_file_radar.py,则 allowed-tools: Bash(python3 *) 不匹配,会被权限拦下。两者必须对齐。$ARGUMENTS。若正文完全没有 $ARGUMENTS,Claude Code 会把参数以 ARGUMENTS: 形式追加到末尾,模型不一定按预期解析。下面的流程图展示了一次调用从用户输入到脚本执行、再到结果解读的全过程,以及动态注入与内嵌脚本两种机制各自介入的位置:
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 ${CLAUDE_SKILL_DIR}/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 控制权限闸。
并非所有字段都和脚本执行相关,下面这张表只挑出最相关的几项:
| 字段 | 对脚本化 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,由人按下按钮再跑。
把前面四步串起来,做一次完整验证:
big-file-radar 已被发现。~/projects/my-app 里最大的 10 个文件"。/big-file-radar ~/projects/my-app --top 10 --csv big.csv。若六步都通过,这个技能就具备了生产可用性。它还可继续演化:加 --since 按修改时间过滤、加 --ext 按扩展名过滤、把输出从 Markdown 换成交互式 HTML(官方 codebase-visualizer 示例正是用打包脚本生成可视化 HTML 的同款套路)。
脚本化 Skill 出问题时,按以下顺序排查,命中率最高:
echo ${CLAUDE_SKILL_DIR} 或临时打印确认替换结果。务必用 ${CLAUDE_SKILL_DIR} 而非相对路径或硬编码绝对路径。allowed-tools 的模式要与实际命令匹配。Bash(python3 *) 不匹配 python script.py(无 python3)也不匹配 ./script.py。/doctor 看描述是否被裁剪;用 /skill-name 手动调用可绕过自动触发问题。--debug 启动能看到错误。stderr,否则模型解析表格式数据时会错乱。把脚本打包进技能,收益与代价并存,需要在三个维度上权衡。
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 基础评测的只读型技能。
${CLAUDE_SKILL_DIR}、动态注入、打包脚本(codebase-visualizer)与权限模型。[!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 等场景全覆盖。
