用YAML统一管理散落脚本:CLI-Anything命令行工具实战解析

发布时间:2026/9/28 16:35:19
用YAML统一管理散落脚本:CLI-Anything命令行工具实战解析 我算是个重度终端用户。早几年手里攒了上百个脚本从a.sh、backup_v2.sh、fix2.py到zzz_final.sh都有真到用的时候全靠history翻翻到还要在脑内回忆一遍这个脚本参数到底是-d还是--dir默认值是啥输出会不会把终端刷爆。后来我花了大半个周末把这些脚本收编成一套工具起名叫 CLI-Anything。说白了它就是把你所有散落的命令行能力统一到一个入口下的工具集核心思路是“用一段 YAML 声明你想让用户怎么调用”参数校验、帮助信息、错误提示、输出高亮这些脏活由核心引擎替你干。适合那些跟我一样天天泡在终端里、又不想反复复制粘贴历史命令的开发者。如果你也是那种“脚本写了不少但管理靠运气”的人这篇内容应该能给你一个比较完整的整理思路。下面我把这个项目的由来、设计拆解、核心实现和踩坑记录全部摊开讲。1. 为什么会有 CLI-Anything从脚本仓库到统一入口1.1 我踩过的脚本管理大坑先说一个真实场景。有一次我要批量改 Nginx 配置里的端口明明上个星期刚写过脚本大概就在某个目录里但硬是找了十五分钟。最后靠grep -rn 8888 ~/scripts才定位到。这种问题不是个别现象脚本一多文件名越来越随缘参数格式各写各的有的接收位置参数有的要环境变量有的干脆跑起来之后再问你一堆问题。等到项目交接给新人的时候对方看到几十个.sh文件完全不知道入口在哪只能挨个打开看注释。更麻烦的是脚本之间经常有重复逻辑。比如“把当前目录下的文件名规范化”“把 GBK 文本转成 UTF-8”“批量压缩某类日志”这些功能散落在不同脚本里有些人用 Python 写有些人用 awk有些人用 Perl修一个 bug 要在三个地方改。时间长了这些东西就不是工具而是包袱。我当时就琢磨是不是可以把它们统一成一个命令入口每个脚本变成一个插件统一管理参数、帮助文档和输出风格。这个想法最终变成了 CLI-Anything。1.2 可能的替代方案与它们的问题在动手写之前我也认真评估过现成方案毕竟重复造轮子不是个光彩的事。结论是它们各有适用场景但都不完全解决“个人脚本博物馆”的问题。方案优势瓶颈Makefile项目内标准化依赖关系清晰跨项目复用很弱缩进和 Tab 问题劝退不少人shell alias / function零成本随开随用没有帮助系统参数校验弱分享给别人时像个黑盒just 命令运行器语法现代支持参数和配方每台机器都得单独装社区生态相对小npm scripts前端生态好JSON 配置简单非 Node 项目用起来别扭命令一多也难维护CLI-Anything统一描述、统一帮助、自动校验需要维护 YAML多一层学习成本我不是说这些方案不好它们在很多场景下非常香。但我的核心诉求很明确有一个自己可控的统一入口新增一个工具只需要丢一个 YAML 文件进去立刻就有了参数解析和帮助文档。这正好是 CLI-Anything 的定位。1.3 CLI-Anything 的价值取舍任何工具都应该有边界否则容易变成四不像。我给 CLI-Anything 设的边界是适合管理 5 到 200 个中等复杂度的日常命令包括文件批处理、文本转换、开发辅助、系统信息查询等。它不适合做的是 CPU 密集型任务的核心逻辑——比如图像处理、大数据跑批这些应该在命令层直接调用原生工具而不是在 Python 里用循环硬算。它的核心价值有四个一个入口、一套参数规则、一种输出风格、零重复造轮子的插件机制。这种取舍让它既能成为我个人的“命令收纳箱”也能作为一个团队公共工具库的基础。2. 整体设计与核心思路2.1 描述即命令把 YAML 当产品说明书落地这个项目时我最大的一个设计决策是不能让人为了加一个命令去写 Python 类或者注册代码。那样门槛太高用起来像在写框架本身。我更希望一个命令等于一个 YAML 描述文本里面有它叫什么、收什么参数、执行什么操作。这就是“描述即命令”。拿一个简单的探活命令举例# ~/.anything/plugins/quick_ping.yaml name: quick-ping description: 快速探测一组域名或 IP args: - name: targets required: true help: 目标地址支持逗号分隔 options: - name: count short: -n default: 4 help: ping 次数 run: mode: shell script: | for t in $(echo {targets} | tr , ); do ping -c {count} $t done这个描述文件的核心信息只有三个部分参数怎么收、命令怎么跑、帮助怎么说。剩下的“怎么解析参数”“怎么把-n 4转成变量”“帮助文本长什么样”全部由 CLI-Anything 的核心引擎接管。这样做的好处是显而易见的你能在十秒内看懂一个命令是干嘛的而不是去翻代码逻辑。新人也只要照着现有 YAML 抄一个就能获得和内置命令完全一致的交互体验。2.2 核心引擎的模块划分CLI-Anything 的核心引擎拆成了五个模块每个模块职责单一这也是我后期加功能时最舒服的部分。配置加载器负责读取 YAML 文件做基础 schema 校验并带有缓存和热重载能力。命令注册器扫描插件目录把每个文件名变成一个命令节点支持子命令和分组。参数解析器基于 argparse/Typer 的语义封装但规则完全由 YAML 里的字段驱动。执行器把解析好的参数注入到run模板选择 shell 还是 python 模式执行并处理超时、重试。输出渲染器统一 stdout/stderr 的样式错误信息标红、警告标黄、正常输出默认色避免每个插件各搞一套。配置加载器是入口命令注册器是骨架参数解析器是门面执行器是发动机输出渲染器是仪表盘。把一个新命令加进来五个模块各司其职不需要改其他代码。这就是我期望的插件体系加功能不破坏原有结构。2.3 为什么选择 Python 实现选 Python 当核心语言很大程度是出于现实考量。第一跨平台能力不错Windows、macOS、Linux 都能跑字符串处理和多字节编码支持也省心。第二标准库里的subprocess、shutil、glob、tempfile都是做命令执行和文件操作的利器不用引一堆第三方包。第三Python 写这种“胶水型”工具特别顺手容易把各个系统命令组合起来。和 Node 相比Python 在调用子进程时更直白subprocess.run一把梭不用折腾各种回调或者 shell 转义。和 Go 相比Python 的开发速度快适合个人项目快速迭代。当然这个选择只是当前偏好CLI-Anything 的 YAML 描述格式其实和语言解耦将来有人愿意用 Rust 或 Go 重写一个核心引擎配置文件完全可以复用。3. 核心功能拆解与实现要点3.1 配置文件格式详解CLI-Anything 的全局配置放在~/.anything/config.yaml内容不多# ~/.anything/config.yaml version: 1 plugins_dir: ~/.anything/plugins defaults: timeout: 30 retry: 1 confirm: falseplugins_dir表示到哪里扫插件defaults是全局默认行为。每个插件文件里最关键的字段是这些字段说明示例name命令名必须唯一name: json-prettydescription一句话描述会出现在anything list里description: 格式化 JSON 文本args位置参数列表args: [{name: input, required: true}]options可选项列表包含short、default、choicesoptions: [{name: indent, short: -i, default: 2}]run执行体支持 shell 或 python 两种模式run: {mode: shell, script: ...}env执行时附加的环境变量env: {PYTHONUTF8: 1}workdir工作目录默认是当前目录workdir: ~/logsretry失败重试次数retry: 2timeout超时秒数超时强制终止timeout: 10参数解析器会读取args和options把用户输入变成模板变量比如{input}、{indent}直接在run里引用。这里有个小设计如果某个 option 没传但 YAML 里有默认值那模板变量默认就取这个值不用写一堆判断。3.2 参数解析器的三种输入类型我在设计参数时只保留了三种输入类型尽量减少使用者的认知负担。位置参数按顺序传值适合必填的核心对象比如文件名、目录、目标地址。开关标记只有有或没有两种状态适合--force、--verbose这种语义。键值选项--port 8080这种带值的选项适合有默认值的可调参数。正常情况下这让anything ls、anything base64 encode hello这种命令读起来像自然语言。参数校验也内置了几种规则choices限定取值范围regex做格式匹配range限定数字区间。比如定义端口参数options: - name: port short: -p type: int default: 8080 range: [1024, 65535] help: 监听端口用户输入anything app --port 99999时参数解析器直接拒绝并提示端口范围而不是等命令跑挂了才报错。这种前置校验的效果是错误在最早的时间点暴露节省调试时间。3.3 执行器如何把一条命令变成流水线执行器是 CLI-Anything 里最容易失控的部分因为命令总是比预期复杂。我最终把它设计成一条相对固定的流水线渲染模板、预处理、确认、执行、捕获输出、判断重试。第一步模板渲染。用户在命令行输入的参数会被安全地注入到run里。这里要注意一个安全细节变量注入时要加引号比如{directory}防止路径里有空格时把命令拆碎。第二步预处理钩子。如果你需要在执行前算个时间戳、生成临时文件可以在run里先写一段pre_script把结果再赋值给模板变量。第三步确认机制。如果你在 YAML 里设置了confirm: true执行器会打印完整命令并问一句“确认执行吗”避免误操作。第四步执行和捕获。shell 模式用subprocess.run(shellTrue)python 模式直接在解释器进程里执行脚本。第五步根据退出码判断是否重试默认失败后不重试但你可以配retry: 2做网络请求这类易错操作。这种流水线让“加功能”变得很机械想清楚你要在哪一层插手然后往对应环节加配置就行不用改执行器本身。3.4 内置小工具的灵感来源CLI-Anything 一开始就内置了几个高频小工具不为炫技而是让用户第一次运行就能看到“统一风格”长什么样。anything file-picker交互式选择文件配合其他命令做批量操作。anything port-check检查某个端口被哪个进程占用。anything json-pretty从标准输入或文件读取 JSON格式化输出。anything ts时间戳转人类可读日期或者反向转换。anything scaffold生成一个新插件的 YAML 骨架降低新手上手门槛。这些命令的共同点是高频、结果简单、容易验证。内置它们的另一个考虑是当作“示例代码”当你想写自己的插件时打开这些命令的 YAML 看一下就知道标准写法长什么样。很多时候示例比文档更有效。4. 从零搭建与实操记录4.1 安装与初始化安装比较简单直接用 pippip install cli-anything装完先跑一次初始化anything init它会创建~/.anything/config.yaml、~/.anything/plugins/目录以及一个最简单的hello.yaml插件。验证是否装好可以运行anything --version anything listanything list会把你当前可用的所有命令列出来每个命令一行带上描述。第一次看到自己的命令树整整齐齐出现在终端里那种“脚本终于有秩序了”的爽感值得体验一下。4.2 手写第一个可用命令base64 编解码我建议新手从base64这种无副作用的命令开始练手。在~/.anything/plugins/base64.yaml里写name: base64 description: 对文本进行 base64 编码或解码 subcommands: encode: description: 编码 args: - name: text required: true help: 要编码的明文 run: mode: shell script: echo -n {text} | base64 decode: description: 解码 args: - name: text required: true help: 要解码的密文 run: mode: shell script: echo -n {text} | base64 -d保存后运行anything base64 encode hello cli-anything anything base64 decode aGVsbG8gY2xpLWFueXRoaW5n第一个命令会把hello cli-anything编码成 base64 字符串第二个命令将其还原。这里故意写的是最直观的 shell 版本但我要提醒你一个坑macOS 的 BSD base64 解码参数是-DGNU 版本则是-d而 Windows 系统压根没有自带 base64 命令。所以更稳妥的做法是用 python 模式run: mode: python script: | import base64 print(base64.b64decode(r{text}).decode(utf-8, replace))这个例子很好地说明了为什么 CLI-Anything 要支持 shell 和 python 两种模式快速验证用 shell跨平台稳妥用 python。4.3 实操案例批量压缩日志文件再来看一个稍微实用点的场景把某个目录里所有*.log文件打包压缩。我早期的写法是# ~/.anything/plugins/tar_logs.yaml name: tar-logs description: 按模式批量压缩日志文件 args: - name: directory required: true help: 日志目录 options: - name: pattern short: -p default: *.log help: 文件匹配模式 - name: output short: -o default: logs.tar.gz help: 输出压缩包名 run: mode: shell script: | find {directory} -name {pattern} /tmp/files.list tar -czf {output} -T /tmp/files.list但很快就发现Windows 上没有find而且临时文件路径也不能写死/tmp。于是我改成了 python 模式run: mode: python script: | import glob, subprocess, os, tempfile files glob.glob(os.path.join(r{directory}, r{pattern})) fd, tmp_path tempfile.mkstemp() with os.fdopen(fd, w) as f: f.write(\n.join(files)) try: subprocess.run([tar, -czf, r{output}, -T, tmp_path], checkTrue) finally: os.unlink(tmp_path)这个版本在三个操作系统上都能跑而且错误会更直观tar 失败了Python 会抛出异常执行器捕获后输出红色错误信息。你可以看到从一个快速原型到跨平台稳定版CLI-Anything 允许你渐进式增强而不是一开始就要写得完美。4.4 自定义插件的加载与调试插件文件名就是命令名.yaml后缀会被去掉。比如base64.yaml注册的命令名是base64tar_logs.yaml注册的命令名是tar_logs。你可以在插件目录里放子文件夹做分组比如web/nginx.yaml、web/cert.yamlCLI-Anything 会把它变成anything web nginx ...这种带命名空间的调用。改完 YAML 之后执行器默认会在每次调用时检查文件修改时间自动重新加载。如果遇到缓存不刷新的情况可以手动执行anything reload调试时有两个选项非常有用。--dry-run只打印解析后的最终命令但不执行适合检查模板渲染结果--verbose会输出每一层流水线的执行日志包括环境变量、工作目录、退出码。比如anything tar-logs ~/logs -p *.log --dry-run --verbose把这两招结合起来95% 的插件问题都能定位到是参数没传对、命令写错、还是环境不对。5. 常见问题与排查实录5.1 配置不生效缓存与热加载很多人第一次用 CLI-Anything 时会遇到“我改了 YAML为什么运行还是旧行为”的问题。原因多半是编辑器保存文件时没有触发正常的文件修改事件或者插件文件名写错了后缀比如base64.yml而不是base64.yaml。这时候先跑anything reload强制刷新再看一眼文件后缀。为了彻底避免缓存坑我在新版里加入了启动时的 schema 校验如果 YAML 有语法错误anything会直接提示出错文件和行号而不是静默加载旧配置。5.2 中文乱码与输出编码中文乱码是跨平台命令工具的头号敌人尤其 Windows。cmd 和 PowerShell 的默认编码和 Python 的 UTF-8 经常对不上导致输出变成一堆问号或者乱码。我的处理办法是三层一起治理首先在全局配置里设置env: {PYTHONUTF8: 1, PYTHONIOENCODING: utf-8}其次在写插件时Python 模式尽量避免print中文而是把原始字节交给输出渲染器统一解码最后在 YAML 文件头部加# encoding: utf-8注释提醒编辑器按 UTF-8 保存。注意Windows 上有时还需要先执行chcp 65001切到 UTF-8 代码页这个我一般放在系统初始化脚本里。5.3 命令找不到与环境隔离如果你在执行插件时收到“缺少命令 xxx”的错误通常是对应工具没装或者不在 PATH 里。执行器在后台已经用shutil.which做过预检所以错误信息比较友好会直接告诉你缺哪个命令。解决办法有几种一是安装对应工具二是在 YAML 的env里追加 PATH比如env: {PATH: /opt/homebrew/bin:/usr/bin}三是干脆在脚本里写绝对路径。我个人的习惯是重要命令尽量用绝对路径或 Python 的shutil.which动态查找避免换机器之后各种“奇怪的不明原因”。5.4 跨平台细节三件套如果要把 CLI-Anything 带到团队或多台机器有三个细节最容易翻车。第一路径分隔符。Windows 用\macOS/Linux 用/硬编码必然出问题。解决方案是shell 模式里要用{directory}时尽量让用户传相对路径python 模式里统一用os.path.join或pathlib.PurePath处理。第二换行符。Windows 的 CRLF 会让一些文本工具行为怪异。建议在输出渲染层统一把\r\n转成\n或者在 Python 模式下用newline控制文件读写。第三文件权限和可执行位。Windows 没有chmod但子进程调用不需要它反倒在 Windows 上要注意.bat和.exe后缀shutil.which(curl)能找到curl.exe但some_script如果没后缀就找不到。这三点理顺之后同一份插件目录基本可以跨平台直接复制。5.5 特殊字符转义问题写 shell 模式时最痛苦的是转义。YAML 里的双引号、命令里的双引号、shell 再解析一次三重嵌套经常让人头皮发麻。我的经验是能用 YAML 的块标量|就绝不用单行字符串因为块标量不会把当成 YAML 结构的一部分。比如script: | echo the value is: {value}而不是写在一行里各种加反斜杠。如果命令里还有更复杂的引号组合我建议直接放弃 shell 模式把逻辑写进plugins/xxx.pyYAML 里只留一行run: {mode: python, script_file: {plugin_dir}/xxx.py}。这不是逃避而是把复杂度放到更适合它的地方。6. 我踩过坑之后沉淀下来的玩法这个项目从诞生到现在我自己从里面收获了不少超出预期的用法最后分享三个个人感受比较深的点。第一刚上手时别急着把所有脚本都收编进来。先挑十个你每周都会用到的命令把它们整理成插件熟悉 YAML 的写法。这十个命令稳定下来之后再慢慢迁移其他低频脚本。如果一开始就贪多很容易因为迁移工作量太大而放弃整个工具。第二给每个命令认真写description和help字段。这不是形式主义。当你的命令数量超过二十个之后anything list就是你的个人速查手册搜索命令靠的就是这些描述。我甚至会把一些“一句话操作说明”写进help比如压缩前会先检查磁盘剩余空间。后来交接工作时新人就是靠这份命令清单快速上手了我的终端工作流。第三我经常用它来沉淀“一次性复杂操作”。有些问题当时解决完命令一敲就忘了但很可能半年后会再遇到。以前我会把它存进博客或云笔记现在我会直接做成一个插件抽象出参数和默认值。这样临时操作变成了长期资产而且因为参数是明确定义的半年后自己回来看也能很快理解当初的意图。另外还有一个很实用的小技巧CLI-Anything 配置了--notify选项后执行长时间任务比如压测、数据同步、批量压缩结束时会自动弹一条系统通知。这样我可以放心切到别的窗口摸鱼等通知来了再回来看结果。这种细节改善往往才是工具真正融入日常的时刻。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询