Skill 脚本语言选型:Python、Node.js、Shell 到底怎么选?

Skill 脚本语言选型:Python、Node.js、Shell 到底怎么选?

为 AI Agent 写 Skill 时,很快会撞上一个看似简单、却很容易拖慢团队的问题:脚本到底用什么语言写?

一个 Skill 通常由两部分组成。一部分是给 Agent 看的说明文档,比如 SKILL.md;另一部分是给 Agent 调用的工具脚本,负责下载资料、解析文件、生成图片、调用接口、批量改写、验证输出。说明文档决定 Agent "知道怎么做",脚本决定 Agent "能不能稳定做完"。

脚本语言选错,不一定马上出问题,但会在维护中反复收税:简单任务写得过重,复杂任务靠 Shell 硬拼;本可以用成熟库解决的问题,最后变成一堆脆弱的字符串处理;本该给 Agent 用的自动化脚本,最后只有作者本人看得懂。

本文围绕 Skill 脚本的真实场景,对 Shell、Python、Node.js/TypeScript 做一次选型对比,并给出一套能落地的决策过程和可直接套用的代码骨架。


一、先定义问题:Skill 脚本不是普通业务代码

Skill 脚本的目标不是构建一个完整应用,而是把一类可重复操作沉淀成 Agent 能稳定调用的能力。

典型脚本包括:

  • 抓取 URL 内容并转成 Markdown
  • 读取 .docx.xlsx.pdf、图片等文件并提取信息
  • 调用外部 API 生成图片、摘要、翻译或发布内容
  • 把 Markdown 转成公众号 HTML、幻灯片、长图或知识卡片
  • 批量处理目录,比如压缩图片、重命名、生成索引
  • 执行项目检查,比如 lint、test、格式校验、结果比对
  • 作为胶水层,串起多个已有 CLI 工具

它们有三个共同特点:

  1. 被 Agent 调用多于被人手动调用。 参数、输出、错误信息必须清晰稳定,不能依赖大量交互。
  2. 任务边界通常很窄。 一个脚本最好只做一件事,输入输出明确,便于 Agent 组合。
  3. 长期维护成本比一次写完更重要。 Skill 会被反复复制、迁移、增强,语言要让后续的人和 Agent 都容易读懂。

所以选型的核心问题不是"哪门语言最强",而是:

对这个 Skill 来说,哪门语言能用最低维护成本,稳定完成最常见的任务?


二、三类候选语言的基本定位

1. Shell:最适合做命令编排

Shell 天然贴近操作系统和 CLI 工具。文件移动、目录扫描、调用现成命令、拼接简单流水线,都是它的舒适区。

  • 适合: 调用 gitffmpegpandoccurljq 等现成命令;做很薄的一层包装(检查参数、建目录、调主程序);简单文件操作;作为入口设置环境变量再调 Python/Node。
  • 不适合: 复杂 JSON/XML/HTML 解析;多步骤业务逻辑;高要求的错误处理;跨平台,尤其是要兼容 Windows;需要单元测试或维护复杂数据结构。

Shell 的最大问题是短脚本很爽,长脚本很痛。一旦出现多层条件、数组、字符串转义、并发、重试和结构化数据,就该换语言。

2. Python:最适合数据、文件和自动化处理

Python 是 Skill 脚本里的"默认稳妥选项"。它语法直接,生态成熟,尤其擅长文件、数据、网络请求、文档格式和图片处理。

  • 适合: 解析生成 Markdown/HTML/JSON/CSV/YAML;处理 Excel、Word、PDF、图片、音视频元数据;数据清洗、批处理、格式转换;调用 HTTP API 并做重试、缓存、错误处理;有一定复杂度但还没到要做应用的业务逻辑。
  • 不适合: 项目本身是前端/Node 生态,复用 npm 包更方便;任务强依赖浏览器自动化且团队已有 Playwright/Node 体系;需要发布成 npm 包并用 npx 一键运行;团队没有 Python 运行时或依赖管理基础。

Python 的优势不是某个单点,而是综合平衡:写得快、读得懂、库够多、适合长期演进。

3. Node.js/TypeScript:最适合 Web、前端生态和工具链集成

Node.js 和 Web 生态天然同构。只要脚本涉及 npm 包、前端构建、浏览器自动化、Markdown/HTML 工具链或 CLI 发布,它往往很顺手。

  • 适合: 复用 npm 生态(unified、remark、rehype、sharp、playwright);操作前端项目(Vite、Next.js、React、Tailwind、ESLint);浏览器自动化、页面截图、DOM 分析、可视化验证;发布成 npm 包支持 npx;团队主用 TypeScript,想要类型约束和工程化。
  • 不适合: 只是简单文件处理,引入 npm 依赖显得过重;要复用大量 Python 数据处理库;不想维护 node_modules、构建步骤和 tsconfig;运行环境不稳定,无法保证 Node 版本和包安装。

Node 的问题不是能力不足,而是容易把一个小脚本写成一个小项目。选 TypeScript 还要额外考虑编译、运行器和依赖管理。

一句话记住三者的分工:

Shell 负责"把命令跑起来",Python 负责"把数据处理对",Node.js/TypeScript 负责"把 Web 和前端生态接顺"。


三、使用场景对比:不同 Skill 怎么选?

使用场景 推荐语言 原因 不推荐做法
简单命令封装 Shell 启动快,贴近 CLI,适合做薄入口 为几行命令引入完整项目
多工具串联 Shell + Python/Node Shell 调度,核心逻辑交给更稳的语言 在 Shell 里写大量解析和分支
JSON/YAML/CSV 处理 Python 标准库和数据处理库成熟,错误处理清晰 sed/awk 硬切结构化数据
Markdown/HTML 转换 Node.js 或 Python Node 有 unified/remark,Python 适合轻量解析和模板 用正则大规模改 HTML
Word/Excel/PDF 处理 Python python-docxopenpyxlpypdfpdfplumber 成熟 Shell 拼命令处理复杂文档结构
图片压缩和格式转换 Python 或 Node.js Python 用 Pillow,Node 用 sharp;看团队生态 Shell 调多个工具却缺错误兜底
浏览器自动化和截图验证 Node.js/TypeScript Playwright 在 Node 生态体验完整 用 Shell 调浏览器解析脆弱输出
调用大模型或外部 API Python 或 Node.js 看官方 SDK、团队栈和部署方式 curl 写复杂重试、鉴权和分页
前端项目检查和修复 Node.js/TypeScript 直接复用项目依赖和 npm scripts Python 绕远路调前端工具链
数据分析和批量报告 Python 数据处理、图表、表格、统计生态更顺 用 Node 重造数据分析轮子
跨平台开发者工具 Node.js/TypeScript npm 分发和 npx 入口更自然 让 Windows 用户跑依赖 Bash 特性的脚本
仅 macOS/Linux 内部小工具 Shell 或 Python Shell 轻,Python 稳;看复杂度 为内部小工具搭复杂工程

四、决策过程:从任务而不是偏好出发

选型最怕的不是选错,而是先有语言偏好再反向解释需求。更稳的方法是按下面顺序判断。

第一步:判断脚本复杂度。 只调用几个现成命令、逻辑少于 50 行,优先 Shell;要处理结构化数据或有复杂分支,优先 Python 或 Node;预计会持续扩展,从一开始就别写成长 Shell。

第二步:看核心依赖在哪个生态。 选型常常不由语法决定,而由依赖决定。依赖系统命令(ffmpeggitpandoc),Shell 做入口可以接受;依赖 openpyxlpython-docxpdfplumber、Pillow、pandas,选 Python;依赖 Playwright、remark、rehype、Vite、ESLint、sharp,选 Node。不要为了"统一语言"放弃最成熟的库,Skill 脚本最重要的是稳定完成任务,不是语言洁癖。

第三步:看运行和分发方式。 脚本写完还要被 Agent、同事、CI 或其他 Skill 调用。先问:用户机器一定有 Python 或 Node 吗?依赖好装吗?要支持 npx 一键运行吗?要进 Docker、CI 或云函数吗?要跨 macOS/Linux/Windows 吗?一般来说,内部可控环境 Python 或 Shell 成本低,面向前端开发者分发 Node 更自然,面向数据/文档团队 Python 更自然,极轻量工具用 Shell 入口加明确依赖检查即可。

第四步:看 Agent 调用体验。 这一条几乎是 Skill 脚本的硬指标,下一节单独展开。

第五步:用决策表收口。

判断问题 选择倾向
只是包装命令、准备环境、移动文件? Shell
处理文档、表格、PDF、图片、数据? Python
浏览器自动化、前端工具链、npm 分发? Node.js/TypeScript
复杂业务逻辑且长期维护? Python 或 TypeScript
需要强类型和团队工程约束? TypeScript
需要最少依赖、最短启动路径? Shell 或 Python 标准库
已有项目主语言非常明确? 跟随项目生态

五、写给 Agent 的脚本长什么样

无论选哪门语言,给 Agent 用的脚本都要满足同一组约定:支持清晰的命令行参数;成功时输出稳定结果,最好是 JSON;失败时返回非零退出码;错误信息说明原因和修复建议;不要求交互式输入;能用 --help 查看用法;关键中间产物有明确路径。

下面是一个"压缩目录下图片"脚本的 Python 骨架,把这些约定都落到代码上:

python 复制代码
#!/usr/bin/env python3
"""压缩目录下的图片,输出每个文件的体积对比(JSON)。"""
import argparse
import json
import sys
from pathlib import Path

from PIL import Image


def compress(src: Path, quality: int) -> dict:
    before = src.stat().st_size
    img = Image.open(src)
    img.save(src, optimize=True, quality=quality)
    after = src.stat().st_size
    return {"file": str(src), "before": before, "after": after}


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("directory", help="待处理的目录")
    parser.add_argument("--quality", type=int, default=80, help="JPEG/WebP 质量,默认 80")
    args = parser.parse_args()

    root = Path(args.directory)
    if not root.is_dir():
        # 错误写到 stderr,正常结果留给 stdout,Agent 不会把两者混淆。
        print(f"目录不存在:{root}", file=sys.stderr)
        return 1

    results, failed = [], []
    for img_path in sorted(root.glob("**/*")):
        if img_path.suffix.lower() not in {".png", ".jpg", ".jpeg", ".webp"}:
            continue
        try:
            results.append(compress(img_path, args.quality))
        except Exception as exc:  # 单个文件失败不拖垮整批。
            failed.append({"file": str(img_path), "error": str(exc)})

    # 结构化输出:Agent 直接解析,不靠读日志猜状态。
    json.dump({"compressed": results, "failed": failed}, sys.stdout, ensure_ascii=False)
    return 0 if not failed else 2  # 用退出码区分"全成功"和"部分失败"。


if __name__ == "__main__":
    sys.exit(main())

关键点不在 Pillow,而在那几处约定:argparse 自带 --help;正常结果走 stdout,错误走 stderr;退出码区分成功、参数错误和部分失败;单文件异常被收集而不是中断全程。Node.js 版本同理:用 process.argvcommander 解析参数,console.log 输出 JSON,process.exit(code) 返回状态。

如果某门语言让这些约定很难满足,那它就不适合这个脚本。


六、依赖与运行:让脚本在别人机器上也能跑起来

脚本能在作者机器上跑,不代表能在 Agent、CI 或同事的机器上跑。依赖怎么声明、怎么安装,直接决定 Skill 的可移植性。

Python:把依赖钉死,别裸用全局环境。 推荐用 uv,它能快速创建隔离环境并锁定版本,Agent 调用前不必关心机器上装了什么。依赖可以声明在脚本头部:

python 复制代码
# /// script
# dependencies = ["pillow>=10,<11"]
# ///

然后用 uv run 执行:

bash 复制代码
uv run compress.py ./imgs --quality 75

如果不用 uv,至少提供一个钉了版本的 requirements.txt(用 pillow>=10,<11 这样的范围,而不是裸 pillow),并在入口脚本里检查依赖是否就位。面向终端用户分发命令行工具时,pipx installpip install 更安全,它会把工具装进独立环境,不污染系统 Python。

Node.js:优先 npx 让用户免安装。 发布成 npm 包后,Agent 和用户都能一行运行,不必先 npm install

bash 复制代码
npx your-skill-cli verify --url http://localhost:5173

本地开发则用 package.json 锁定依赖,并用 npm ci(而非 npm install)在 CI 里按 package-lock.json 精确还原。.mjs 后缀可以省掉一部分 ESM/CJS 配置纠结。

Shell 入口:先检查,再执行。 Shell 脚本最常见的崩溃来源是"假设某个命令存在"。薄入口应该先验证依赖和参数,再调主程序:

bash 复制代码
#!/usr/bin/env bash
set -euo pipefail  # 出错即停、未定义变量即报错、管道错误不吞掉。

command -v ffmpeg >/dev/null || { echo "缺少 ffmpeg" >&2; exit 127; }
[ $# -ge 1 ] || { echo "用法:run.sh <输入文件>" >&2; exit 2; }

exec python3 "$(dirname "$0")/process.py" "$@"

set -euo pipefail 是 Shell 入口脚本的标配,它把"静默出错继续跑"这个最危险的默认行为关掉了。

跨平台是另一条隐线:依赖 Bash 特性(数组、[[ ]]set -o pipefail)的脚本不要指望 Windows 用户能跑。需要真正跨平台时,选 Node 或 Python,而不是给 Shell 打补丁。


七、典型决策案例

把前面的判断套到几个常见 Skill 上:

  • 公众号文章发布 → Python。 涉及读 Markdown/HTML、上传图片、调用 API 创建草稿、区分 token/图片/格式/接口错误。文件、HTTP 和错误处理密集,后续还会扩展成批量发布和素材复用。Shell 可做入口检查环境变量,但核心逻辑不该写在 Shell 里。
  • 前端页面截图验证 → Node.js/TypeScript。 启动 dev server、打开页面、截图、检查 canvas、验证移动端和桌面端布局。Playwright 和前端项目同生态,能复用 npm scripts、Vite、Next.js,DOM/CSS/截图处理更直接。Python 也能跑 Playwright,但项目已是前端工程时 Node 更顺。
  • 图片压缩 → Python 或 Node.js。 熟 Python 用 Pillow,追求性能和 Web 图片处理用 Node 的 sharp。Shell 可做遍历,但一旦要汇总报告和错误统计,Python/Node 更稳(参见第五节骨架)。
  • 下载 URL 转 Markdown → Node.js 或 Python。 依赖浏览器渲染、DOM 清理、Readability、HTML 到 Markdown 工具链时选 Node;主要是请求、解析和文本清洗时 Python 也够。不要用 Shell 加正则处理 HTML,HTML 不是普通字符串,越处理越碎。
  • 项目初始化 → Shell 或 Node.js。 建目录、复制模板、装依赖、初始化 git、打印下一步。仅面向内部 macOS/Linux,Shell 最简单;要公开跨平台分发,Node 更适合做 CLI 并用 npm 管版本。

八、推荐的组合模式

实际项目里不必强行单语言。更健康的结构是按职责分层:

text 复制代码
skill/
├── SKILL.md
├── scripts/
│   ├── run.sh       # 薄入口:检查参数、设置环境、调主脚本
│   ├── publish.py   # 核心逻辑:文档、图片、API、错误处理
│   └── verify.mjs   # 需要浏览器或前端生态时用 Node
└── templates/

混合结构的关键是边界清楚:Shell 不写复杂业务逻辑,Python/Node 不重复做系统命令编排;每个脚本都有独立 --help;脚本之间通过文件路径、JSON 或明确参数通信,不靠"读某段日志文本"传递关键状态


九、给团队的默认规则

为了减少每次争论,可以把上面的判断固化成一套默认规则:

  1. 复杂脚本默认用 Python。 文件解析、数据转换、API 调用、批处理、报告生成,优先 Python。
  2. 薄入口默认用 Shell。 只做环境检查、命令编排、调主脚本,不堆业务逻辑;开头加 set -euo pipefail
  3. Web/前端/浏览器自动化用 Node.js/TypeScript。 Playwright、前端构建、Markdown/HTML AST、npm 分发优先 Node。
  4. 超过 100 行的 Shell 必须重新评估。 不是硬上限,但通常说明它已从"胶水"变成"程序"。
  5. Agent 调用体验优先于个人偏好。 参数、日志、退出码、结构化输出和可复现性,比语言喜好重要。
  6. 跟随核心依赖,而不是流行度。 哪个生态有最成熟、坑最少的库,就选哪个。

归根到底,成熟的选型不是押注某一门语言,而是让每个脚本都站在最适合的位置上:入口轻、核心稳、依赖顺、输出清楚。 这样 Agent 调用起来稳定,人维护起来也不累。

相关推荐
Heracles10241 小时前
一篇文章教你学会MCP
后端
ZhengEnCi1 小时前
09d-斯坦福 CS336 作业三:缩放定律(Scaling Laws)
人工智能
范闲1 小时前
Charmbracelet TUI 生态系统指南
后端
JieE2121 小时前
从"无状态"到"懂你":深入理解 LLM 对话的本质,以及 Prompt/Context/Loop 三层工程进化之路
人工智能·llm·ai编程
稚雪九月1 小时前
永久记忆,丰富情感,Atrium AI框架:给AI一颗真正的心
人工智能
颜进强1 小时前
AI性能参数-截断、延迟与流式输出
前端·后端·ai编程
小鼻子的猫1 小时前
万字长文讲透 AI Agent 架构设计:从 ReAct 到多 Agent 协作,附完整 Python 代码
人工智能
浮游本尊1 小时前
Java学习第44天 - 本地二级缓存 Caffeine、Redis 分布式锁与热点 Key / 库存预扣
后端
Hector_zh1 小时前
实战·第八篇:当模型陷入死循环——FACA破解JSON生成的架构陷阱
人工智能·agent·vibecoding