1. 从“到处找命令”到“万物皆CLI”CLI-Anything 的诞生背景如果你常年泡在终端里大概会有一个相同的烦恼这周要查代码仓库的提交统计下周要给某个接口发测试请求再下周可能只是想让一组 JSON 数据变成漂亮的表格。每次做这些事情我都要临时去找脚本、翻历史记录、回忆参数最后在终端里拼出一条长得像密码的命令。次数多了我就萌生了一个想法——能不能让“任何东西”都统一成一个命令叫出来就用这就是 CLI-Anything 的出发点一个不限定业务形态的命令行封装层把所有可重复做的事都变成“子命令”。它不是一个业务软件而是一个机制你给它一份简单的声明文件它就能在终端里生成一个带参数解析、帮助提示、输出格式化的 CLI 命令。你可以用它包装 GitHub 仓库搜索流程、封装内部 API 调用、把某个复杂的 node 脚本变成可交互命令、甚至把日常 Git 操作组合成语义化的短命令。在动手前我先想清楚一个问题市面上的 CLI 框架已经很多了为什么还要再造一个Commander、Yargs、oclif、Cobra 我都用过它们解决的问题是“帮你写好一个程序的标准外壳”。但 CLI-Anything 的角度完全不同——它把“定义命令”这件事从代码里抽出来变成运行时发现和加载。这意味着你不需要重新编译、不需要发版往某个目录里丢一个 JSON 文件一个新命令就能生效。这个性质才是它存在的理由。这篇文章不打算写成一个完整的项目教程而是想把我设计它的时候踩过的一些坑、做过的选型、以及对“CLI 工具”这件事的理解一起说清楚。里面会涉及一些代码和目录结构但更重要的是每一步取舍背后的原因。1.1 目标用户和适用场景先说清楚它能帮到谁。对我自己来说它解决的是“多项目脚本管理”的混乱每个仓库里都有一堆 npm scripts跨项目想复用某个能力就很麻烦。CLI-Anything 把脚本提升到用户目录这个级别任何项目里都能直接调用。第二种是团队内部分享场景。你不想让每个人都学会写 Node 或者 Python但想让大家都能跑一个数据生成、代码格式化、环境检查的任务。用 CLI-Anything 注册一条命令团队成员只需要anything check-env就能跑通。第三种是个人效率工具的集合扩散。很多开发者会在~/.zshrc里堆积大量 alias 和函数时间一长维护成本极高。一个统一的命令注册表加上参数解析和帮助信息比 alias 的“字符串替换”灵活得多。1.2 为什么不直接用现成的脚本语言有人会说这些事用 Shell 脚本加 alias 也能做啊。确实能但那次第的体验完全不同。Shell 函数只适合处理“固定的一段操作”。一旦涉及到参数类型校验、必选参数提示、多选项组合、子命令嵌套Shell 的$1、$2处理方式就会变得难以维护。更别提出错提示、查询某个命令支持哪些参数这类交互能力。而 CLI-Anything 的核心价值是“声明式定义”参数怎么写、命令怎么执行、输出怎么格式化都通过结构化数据描述。代码放在了执行层使用者只描述“要什么”不用关心“怎么解析”。所以我的结论是它不是替代 Shell aliases而是替代你“从零写脚手架”这个过程。它适合那些逻辑有一定复杂度、需要反复调整参数、并且希望能被团队快速上手的任务。2. CLI-Anything 的架构核心命令引擎、类型系统与插件接口的三角关系真正开始写代码之前我画了一下整体的数据流。一条命令从被敲入到产生结果大概经过四个阶段命令解析、参数校验、执行器调度、输出处理。CLI-Anything 把每一阶段都抽象成一个接口这样任何“东西”都可以被塞进这条流水线。实践下来这个架构要把三层关键设计想清楚命令引擎负责子命令分发类型系统负责参数解析和自动补全插件接口负责让各种“执行物”本地脚本、远程 API、二进制程序都能被调度。2.1 引擎层运行时发现而不是编译期注册传统 CLI 框架里命令都在 main 函数里静态注册。CLI-Anything 反过来根命令启动时扫描注册目录读取所有可加载的命令描述文件动态构建路由表。注册目录的默认结构长这样~/.cli-anything/ ├── registry.json ├── commands/ │ ├── gsearch.json │ ├── weather.json │ └── bump-version.json └── scripts/ ├── gsearch.js ├── weather.js └── bump-version.shcommands目录下每个.json文件描述一条命令scripts目录存放实际执行逻辑。引擎启动后递归扫描commands读取其中的name、description、params、run字段构建一张{ commandName: commandDefinition }查找表。这里有个细节容易被忽略加载时机。我建议不要一次性把所有描述文件和脚本模块全部加载而是采用“注册时只读元信息执行时才加载具体模块”的延迟策略。原因很简单——命令多了以后全量加载会让 CLI 启动变慢而且某些脚本的依赖冲突可能在启动时就炸掉影响所有命令。延迟加载的实现也不复杂只是把run字段中的路径存下来等到用户真正敲了这条命令再 require 或者执行子进程。2.2 类型系统参数不再是--keyvalue那么粗暴CLI-Anything 的参数定义本质上是一套迷你 JSON Schema。每个参数可以声明类型、默认值、必填、枚举范围甚至关联一个生成函数。比如{ name: search, description: 搜索某个归档代码库, params: [ { name: query, type: string, required: true, description: 搜索关键字 }, { name: scope, type: enum, values: [repo, issue, wiki], default: repo, description: 搜索范围 }, { name: limit, type: number, default: 10, description: 结果数量 } ] }引擎会根据type自动做解析和校验。传入--limit abc的时候直接给出类型错误--scope unknown-value会提示合法取值。这对使用者的友好度提升是巨大的——你不再需要记住一堆参数约定CLI 自己会告诉你哪里写错了。我在这里复用了一套 inference 机制如果参数值看起来像是布尔值就转成 boolean如果像数字且类型声明为 number就做数字转换。但这套推测必须谨慎不能过度聪明。实际操作中我尽量以显式声明的类型为准推测只用于没有定义参数的快捷键场景。2.3 执行器local script、binary、API proxy 三种模式描述文件里的run字段决定任务怎么执行。我设计了三类模式分别应对不同场景。local script 模式最常用指向本地磁盘上的.js、.py、.sh文件会通过子进程启动并把解析好的参数以--keyvalue方式打平传递。它适合复用已有脚本或者用某种特定语言完成的逻辑。binary 模式允许你调用任意可执行文件。比如你仓库里有一段 Go 写的编译好的工具你完全不需要管它是怎么做出来的CLI-Anything 只负责包装和传参。这个模式对已有企微工具链的整合特别管用。API proxy 模式比较有意思它允许你抓取一个 URL 请求格式的地址。参数会映射成 URL query 或路径占位符最终通过 HTTP 请求完成调用输出可能是 JSON 或其他格式。比如我这个工具的示例命令之一就是调天气接口anything weather --city北京 --unitmetric三种模式封装后对外完全一致使用者感觉不到区别。3. 四种定义方式JSON 配置、API 映射、脚本注册与交互式生成上一节讲的是定义结构现在说“生成这些定义文件”的途径。CLI-Anything 的进阶设计是提供了四种从“原始素材”到“命令文件”的转换方式这样你不需要手写一堆 JSON很多东西可以在命令里直接生成。3.1 方式一手写 JSON 配置最基础也最灵活手写 JSON 适合精确控制。起初我一遍遍地折腾模板文件后来把格式稳定下来之后手写成本并不高。一个典型的 JSON 描述文件只有name、description、params、run四个区块字段不超过十五个。这个文档在团队内分享特别适合别人一看就知道这个命令做什么、能传什么参数。如果某个命令需要加一个参数只需要在params数组里加一项保存后立刻生效不用改任何主程序代码。3.2 方式二从常见 API 映射命令半数场景下我们要封装的其实是某个 HTTP 接口。手动把 query 参数写进 JSON 并不复杂但真的很烦。CLI-Anything 提供了anything api2cmd内置命令你只要给它一个 OpenAPI/Swagger 格式的接口描述文件路径它就能把某个 endpoint 自动转化为命令定义。这个转化逻辑会读取接口的参数列表query、path、header转换成本地命令的参数并设置对应的 HTTP 方法、路径模板。比如有一个GET /v1/projects/{id}/builds转化后你会得到anything builds --id123 --statusfailed自动映射省了体力但我必须提醒OpenAPI 里可能有一些只依赖服务端环境、不适合 CLI 暴露的参数有些甚至可能是密文所以自动生成的命令必须人工 review 一遍把不必要的字段去掉再添加默认值。3.3 方式三把历史执行记录变成可复用脚本这是我自己很喜欢的一个功能anything capture。平时在终端里敲过一段命令序列但没来得及封装成可复用工具。Capture 的原理是启用一个 shell hook记录进入交互模式的命令历史按时间分段。当你发现某段操作是“最近频繁重复的”就可以手动把这个时间段内的命令序列沉淀成一条新命令。这类“捕获”可以在两个层次上做一是 Shell 历史记录级别的捕获只保留命令文本二是执行器级别的封装把每步执行纳入一个中间状态机。目前我实现的是前者配合一定的手工整理。实际使用下来它很适合把“部署前例行检查”这种多步骤操作整合成单条命令。举一个具体例子anything capture start # 执行 git status拉取最新代码运行测试 anything capture stop --name check-before-deploy之后就会生成一个包含若干 shell 语句的scripts/check-before-deploy.sh并在commands/下生成对应的命令描述你可以稍加修改比如把测试命令换成带参数版本的。3.4 方式四交互式生成向导最后一种生成方式是给懒人准备的。运行anything new会进入一个向导先问命令名称、描述然后逐个询问参数名、类型、是否必填最后问你“执行器要用哪种模式”并提供一个代码模板。回答完成后文件写入对应位置命令立刻可用。这个方式对新手特别友好。很多人第一次见到 CLI 框架的“定义文件”时不知道怎么下手交互式生成规避了格式问题还在最后一个步骤帮你生成了可运行的 hello world 脚本。4. 从仓库搜索到本地命令一个完整 Demo 的复现过程理论说多了容易飘我拿一个具体场景演示 CLI-Anything 的完整搭建过程。假设我想把“在某个 Git 代码仓库里搜索并汇总代码改动”变成一个团队可用的命令功能是接受一个路径参数、一个日期区间返回这个区间内每个文件的变更数量并排名。4.1 先写执行脚本这部分用 Node 写一个独立脚本逻辑很简单// scripts/git-report.js const { execSync } require(child_process) const [repoPath, since, until] process.argv.slice(2) if (!repoPath) { console.error(仓库路径不能为空) process.exit(1) } const cmd git -C ${repoPath} log --since${since} --until${until} --prettyformat: --name-only const output execSync(cmd, { encoding: utf8, maxBuffer: 10 * 1024 * 1024 }) const files output .split(\n) .map(x x.trim()) .filter(x x.length 0) const stats new Map() for (const file of files) { stats.set(file, (stats.get(file) || 0) 1) } // 输出一个简单的 JSON 报告 const result [...stats.entries()] .sort((a, b) b[1] - a[1]) .slice(0, 20) .map(([file, count]) ({ file, count })) console.log(JSON.stringify(result))这里注意执行脚本用的是process.argv.slice(2)获取外部传入的参数——因为脚本是独立进程参数来自子进程调用。我不建议直接依赖process.env传参因为 Windows 下环境变量长度有限制还是命令行参数最稳妥。4.2 编写命令描述并调试接着写相应的描述文件{ name: git-report, description: 统计某仓库指定时间段内变更最多的文件, params: [ { name: repoPath, type: path, required: true, description: 仓库本地路径 }, { name: since, type: date, required: true, description: 起始日期 }, { name: until, type: date, default: now, description: 截止日期 } ], run: { type: local-script, file: scripts/git-report.js } }保存后直接执行anything git-report --repoPath/tmp/myrepo --since2024-01-01CLI-Anything 会完成远程脚本加载、参数类型校验、默认值填充最后以表头 JSON 结果的形式打印出来。首次跑的时候有几个坑值得一提path类型参数如果没做规范化Windows 下盘符路径很容易出现反斜杠转义问题。我的做法是在引擎层对path类型统一做path.resolve()转换这样对于依赖相对路径的脚本更友好。4.3 参数映射的“一帆风顺”背后如果你照着这个 demo 跑大概率第一次就通了。但真正复杂的是上面没展开的when 参数没有提供时default: now是怎么被解析成真实日期的在 CLI-Anything 的类型系统里我内置了一组“自定义类型解析器”。date类型面对的输入可以是2024-01-01、now、last-week这种相对表达。解析器会先判断是不是关键字再做相对时间计算。你完全可以把这套逻辑替换成自己的——类型定义里可以指定parser: customDate引擎会查对应的解析函数注册表。这表明一个原则CLI 框架提供默认类型但真正的灵活性来自类型的扩展性。一个类型不仅能解析还影响了后续的补全行为、格式校验、甚至 UI 提示。5. 打包分发与跨平台兼容你在 Windows 上踩过的坑我在 CLI-Anything 里都踩过一遍CLI 工具做出来是给自己用的还挺容易一旦团队要用、要发到内部包管理器跨平台问题立刻浮出水面。这一章我重点说说分发和兼容性治理。5.1 包结构设计npm vs homebrew vs 自建 registryCLI-Anything 本身是用 Node 写的所以最自然的打包方式是发布到 npm利用bin字段暴露全局命令。对于使用 npm 的团队一条npm install -g cli-anything就可以完成安装。但如果你服务的对象不是前端开发团队让所有人装 Node 会很痛苦。我的建议是提供两套分发渠道核心引擎走 npm 包面向普遍用户的部分用 HomebrewmacOS和 ScoopWindows的仓库文件指向同一个 npm 包。实际上 Homebrew 里许多工具包的底层仍是 npm 包只是用depends_on node声明了依赖。分发方案对比表渠道适用人群更新便捷性需要预装环境npm 全局安装Node 开发者高npm 自动管理Node.jsHomebrew 公式macOS 用户中依赖 release 流程HomebrewScoop manifestWindows 用户中Scoop直接拉仓库源码内部二次开发低手动同步Git Node.js5.2 Windows 下路径分隔符和 shebang 的常态陷阱开发期我主要在 macOS 上做第一次让团队在 Windows 上跑的时候崩了好几处。最大的问题是路径分隔符。Node 内部操作基本都是统一格式没问题但当你把命令参数传给子进程时Windows 上如果路径里有C:\Users\xx\repo直接在 gin 调用execSync可能会被转义处理影响。经验之谈只要涉及子进程命令拼接统一用spawn而不用execSync并且给参数加一层 JSON stringify 风格包裹可以避开大部分转义问题。还有 shebang 的问题npm 全局安装的 bin 脚本在 Windows 下靠 npm 自己生成的.cmd垫片来运行。这个机制大部分情况是自动的但如果你在某个脚本里写了#!/usr/bin/env node然后直接在 CLI-Anything 的 local-script 模式里调用这个.js文件Windows 可能不会识别这个 shebang导致用默认程序打开。我现在的代码里会额外检测当前平台Windows 下强制用process.execPath调用 Node 来执行脚本。5.3 跨平台命令测试清单测试跨平台能力时不要只在装好的环境里跑一遍 happy path。我建议你按这份清单检查参数路径包含空格、中文、括号时是否正常命令注册目录在 Windows 的%USERPROFILE%和类 Unix 的$HOME下是否能正确解析--outputjson输出在终端里会不会因为编码问题乱码Windows 下建议强制process.stdout使用 utf8命令执行失败时错误码是否一致注意process.exitCode和直接process.exit(1)的区别是否监听了SIGINT用户按 CtrlC 时中间状态的临时文件如何清理我和团队最终得出的结论是CLI 工具跨平台的主要成本不在功能代码而在“参数传递路径”和“输出编码”这一层。所以现在核心引擎里所有和路径、编码相关的操作都抽成了一个独立的兼容层每个 PR 都要过这三个维度的测试。6. 云端集成把 CLI 变成自动化任务的“胶水层”CLI-Anything 目前最大的价值不只是交互式使用。把它用在自动化任务里它瞬间变成胶水层把不同场景的脚本串联起来、把散落的操作固化成可观测、可监控的动作。6.1 定时任务和事件触发因为所有命令都是“一条命令一组参数”你几乎可以直接把命令粘贴进 crontab 或者 GitHub Actions 的 step 里。不需要额外写 wrapper。比如我想每天早上十点检查文档站链接是否全绿0 10 * * * anything healthcheck --sitedocs --threshold98又比如配合 CI在每次发版前跑一个anything preflight --envstaging让命令输出 JSON 格式的检查结果后续步骤直接解析这个 JSON 判断是否继续。这一设计意味着 CLI 工具无需为了自动化而改变自身逻辑它本来就是无状态的、参数驱动的。非常重要的一点是一定要保证 CLI 的输出是结构化的。交互式输出给眼睛看的格式化表格是“人读模式”而--outputjson是“机器读模式”。这个模式必须在任何命令上都可用这样自动化任务才有稳定的解析目标。6.2 把常用 GitHub 操作组合成命令我实际用得最多的是一个gh的增强命令跨仓库搜索项目时常去 GitHub 的 Web 搜索页但是参数构造很麻烦。有了 CLI-Anything我可以注册一个命令映射到 GitHub 搜索 API。{ name: ghsearch, description: 在多仓库中搜索指定关键字, params: [ { name: keyword, type: string, required: true }, { name: org, type: string, default: }, { name: limit, type: number, default: 10 } ], run: { type: api-proxy, method: GET, url: https://api.github.com/search/code, query: { q: $keyword org:$org, per_page: $limit } } }然后跑anything ghsearch --keywordcli --orgmycompany --limit20就拿到代码片段。配合--outputjson还能交给 jq 继续处理。这种“翻译 聚合”的场景CLI-Anything 比每个平台一个一个写定制化脚本要省太多事。6.3 与监控和告警系统对接如果你手头有监控系统比如 Prometheus Alertmanager 之类CLI-Anything 命令作为“诊断命令”很有价值。告警来的时候你第一时间在终端里跑一条命令拿到最关键的上下文而不是去翻 Dashboard。我用的模式是对关键服务定义一组diag-*命令返回固定的 JSON 结构节点状态、最近错误、关键指标并把输出自动写到/tmp/cli-anything-diagnose.log后续如果要手动追查也有原始记录。这其实是对“可观测性”一种比较朴素的补充但它完全不需要给服务端装 agent只靠一个通用 CLI 就能随时取到数据。7. 进阶优化让命令像原生命令一样好用如果只是“能跑”市面上大部分框架都能做到。CLI-Anything 真正拉开体验差距的地方在于它围绕使用手感做的一系列优化。这一章挑选三个最重要的优化方向展开。7.1 命令别名与动态补全命令名太长会影响输入效率所以引擎支持在描述文件里定义aliases字段。但比别名更重要的是动态补全。zsh 的compdef和 bash 的complete都能做补全可是静态补全对 CLI-Anything 动态发现的命令不太适用。我的做法是让 CLI-Anything 自己生成补全脚本每次扫描注册目录后生成一个当前命令列表及其参数的补全脚本然后输出到标准位置zsh 是~/.zsh-completion/_anything。当新增一个命令时补全脚本需要重新生成。动态补全还有一层参数值本身也很可能是动态的。比如一个命令的参数--project-id你希望用户按 Tab 的时候露出现有项目列表。这个能力通过在参数定义里加completions: {type: api, url: https://.../projects, valuePath: data[].id}来实现补全时需要发起异步请求。实现起来比静态复杂一点但效果非常惊艳用惯以后回不去“看文档敲参数”的日子。7.2 结构化输出与管道友好我前面提到--outputjson但这只是开始。CLI-Anything 的输出层定义了一组输出模式pretty表格、json、silent只输出错误。为了让输出真正适合管道我做了两件事一是所有数据先组织成中间结构再交给输出渲染器——这意味着你换一种输出格式时不需要改命令实现二是 stdout 和 stderr 严格分开数据走 stdout日志和警告走 stderr。这俩规则听着简单但实际很多工具没做好导致anything cmd | jq .的时候被日志干扰。7.3 状态持久化与上下文感知命令执行过程中会有一些状态需要保存上次登录的 token、用户偏好、某个轮询任务的状态。传统命令行工具喜欢把这些写到用户目录的繁碎文件里时间长了到处都是。CLI-Anything 提供的是一个统一状态存储anything env set key value存储在后端的一个 SQLite 文件也可以切成 JSON 文件。命令执行时可以通过$ctx(key)模板变量引用这些值。这个设计带来一个实际好处在交互式会话里输入敏感信息比如 API token后你不想重复输入更不想把它写在 Shell 历史记录里。把它存进状态存储并在参数定义里标记为sensitive: trueCLI-Anything 会自动从历史记录过滤掉这个词并且执行时通过环境变量传给子进程而不是作为明文命令行参数否则会被ps看到。8. 踩坑记录与优化思路本地开发中那些反直觉的问题最后这一整章我想专门分享一些构建 CLI-Anything 过程中让我印象深刻的坑。很多问题不是查文档能找到答案的完全是在真实迭代里踩出来的。8.1 “快速”不等于“立即同步加载”第一版实现我图简单启动时同步递归扫描所有命令目录遇到不存在的文件直接抛错退出。这导致一个悲剧团队里有人不小心写坏了一个命令描述文件其他人所有命令都跑不了了。现在的实现做了一层“隔离加载”某个命令定义加载失败时只影响该命令本身引擎仍然正常启动但在帮助列表里标记为[broken]。如果你运行它会得到针对性的错误报告告诉你是 JSON 解析失败、脚本不存在还是参数定义非法。这个是 CLI 工具从“个人脚本”进化到“团队工具”非常重要的一道门槛。8.2 子进程超时与僵尸进程问题本地 script 模式中如果你调用的脚本报了错但没有正确退出CLI-Anything 这个父进程可能因为等待子进程而一直挂着。尤其是脚本里派生了异步子任务的情况你需要考虑给执行器加超时控制。我的处理办法是在执行本地脚本时默认设置命令级的超时时间。如果一个命令预期耗时较长可以在定义里声明timeout: 120。超时后引擎杀掉子进程分发进程并且输出一个“命令超时”的提示。Linux/macOS 下可以用child.kill()Windows 下如果杀不死子进程建议用任务计划的方式间接控制超时或者使用tree-kill这类库把整个进程树都清掉。8.3 尽量用异步别阻塞事件循环CLI 工具通常生命周期短很多人不太在意异步。但 CLI-Anything 里有些命令会同时调用多个 HTTP 接口或者多个本地脚本这时候并发性能直接体现在用户体验上。我一开始写的api-proxy执行器是同步逐条请求的后来改造成基于Promise.allSettled的并发模型。参数支持--concurrency5批量场景下速度提升了十倍以上。当然并发高了之后也带来一个问题多个子进程同时写终端输出时内容会穿插。所以还要引入一个简单的输出队列机制保证每个子进程的完整数据块按顺序打印。8.4 社区化和测试策略最后说一下测试。CLI 工具的自动化测试容易做成“快照测试”把命令输出和预期文本比对。但输出格式稍有变化测试就崩。我更推荐从“逻辑正确性”出发命令执行后产生的副作用才是断言的关键。比如执行anything git-report真正应该验证的是“返回结果中是否包含特定文件变化数量”而不是“输出文本是否等于预期字符串”。为此我在引擎层抽象了一个虚拟执行环境命令仍然可以执行但脚本文件可以被替换成测试替身。这让单元测试快了不少而且不依赖真实的 Git 仓库。9. 后续演进CLI-Anything 还能变成什么样至少在这一轮迭代中CLI-Anything 的结构已经稳定下来。它的下一步有几个明确的方向第一是插件市场既然命令定义只是 JSON 文件那天然就可以分享。如果有一个共享注册表一条命令anything install search就能从别人那里拉取一个命令定义和相关脚本使用体验有点像 npm 但只针对命令。这里必须考虑安全问题——执行的可是任意代码所以签名校验和沙箱执行机制要先于市场功能出现。第二是更深的交互能力目前交互只发生在“参数缺失时提示输入”。下一步可以支持子命令内部的分步向导、参数输入时的即时建议、以及出错时的自动纠错。很多重复性操作其实不需要一次成型而是要在多轮调整中逐渐打磨成稳定流程。第三是“知识库”能力CLI-Anything 记录了所有命令的执行历史包括成功失败状态、执行耗时、参数快照。这部分数据如果聚合起来能回答诸如“这个命令最近一周失败率为什么升高”“团队最常用的命令是哪个”的问题。这就从个人效率工具变成了团队效能管理入口。这些让 CLI-Anything 不只是一个工具而是一套持续生长的生态。回过头来看做这个项目最大的收获倒不是某个技术实现而是对“把任何任务变成命令”这件事有了更深的理解。真正好用的 CLI 工具不是把界面藏起来而是把你和机器的对话模式固定成一个个可以重复、分享、验证的单元。我现在几乎每天都会往自己的命令注册表里加一个新命令有些是临时脚本有些则会在以后的很多年里反复使用。我个人的习惯是每当一个操作第三次出现在我的历史记录里我就把它capture下来沉淀成正式命令。几年之后回看这其实就是一份最真实的个人数字化操作手册。