说实话最开始我给自己搭这套叫 CLI-Anything 的东西纯粹是受不了了。电脑里堆了几万张照片要按日期归档视频素材要批量抽帧日志文件要每天扫一遍找异常再加上零零散散的 JSON 数据处理和接口连通性检查——每一个单独拎出来都有对应的图形工具但每次都要打开窗口、点菜单、选文件、调参数干一次两次还行让我连续干五十次我真能崩溃。命令行工具恰恰能治这种病。CLI-Anything 不是我发明的某个高深框架它更像是我给自己定的一条规矩一切高频、机械、可批量、可参数化的操作都必须有一个终端入口。文件整理、媒体处理、系统巡检、文本处理、网络检查全部收进同一个命令体系里统一用法、统一输出、统一退出码。这个项目适合所有日常跟电脑打交道的人哪怕你完全不懂编程跟着把工具装上、把脚本放进去也能立刻感受到一条命令干完以前十步操作的爽快。这篇文章我会从设计思路、骨架搭建、核心模块实现到一路踩过的坑完整地拆开讲一遍。你会发现真正有价值的不是某一两个脚本而是那套放下一个文件就能多出一条命令的机制。1. 为什么非要把所有事情都塞进终端先说清楚一个事情CLI-Anything 不是要让终端取代一切 GUI 工具。图形工具在图片预览、视频剪辑、复杂排版这些场景里依然不可替代。我主张的边界很明确——凡是重复发生两次以上的操作就值得脚本化凡是能通过参数描述的操作就值得 CLI 化。拿我自己的例子来讲。以前整理相机导出的照片我的操作流程是打开文件管理器、按类型排序、手动建文件夹、一张张看时间戳、拖拽移动。半小时过去手酸眼也花了。后来我写了一个批量重命名脚本输入一个匹配模式输出一类新文件名参数一给回车几十秒结束。这种差距不是快一点的差距是愿意做和不愿做的差距。CLI 相比图形界面有两大不可替代的优势。第一是组合性。命令行工具可以用管道串起来比如find找出文件交给ffmpeg批量转码再让rsync推送到备份位置一个就能把一条流水线完整跑完。图形工具之间的数据流转永远没有这么流畅。第二是脚本化。命令一旦写好就能放进cron定时任务、挂到 Git 钩子上、被其他脚本调用实现真正的无人值守。我现在的每日巡检就是早上七点自动跑一遍系统状态检查结果写到日志文件里根本不需要我动手。还有一个常被忽略的好处CLI 工具占用资源极低而且大部分是单文件、零依赖、纯文本配置。相比动辄几百 MB 的图形应用一个 10KB 的脚本就是个文本文件翻看、修改、交给别人都极其轻量。当然边界感要有。需要人眼判断的、需要拖拽微调的工作我不会硬往命令行里塞。比如调色、做海报、剪多轨视频老老实实开 GUI。CLI-Anything 的目标是包揽那些看得见规律的机械活把时间还给你去处理真正需要人的事情。2. 骨架设计一个入口加一堆小脚本才是长久之计刚开始我没想这么多随手把各种脚本堆在~/scripts/里命名随意参数风格也不统一。用了一个月就发现问题我要么想不起命令名要么搞混参数顺序要么在几个脚本里重复维护同一段逻辑。于是重构成了现在这套骨架。2.1 为什么是目录即命令库CLI-Anything 的核心设计很简单一个入口脚本cli加上一个存放命令的目录commands/。目录下每一个可执行的.sh文件就是一条独立命令。文件名就是命令名文件内的注释就是帮助文本文件体就是逻辑实现。为什么不用一个大脚本包揽所有功能因为拆分之后每个命令都可以独立测试、独立修改、独立复制给其他人。你新写一个脚本丢进目录不需要改动入口代码新命令立刻生效。这个放下就能用的机制是整个工具集能持续生长而不腐烂的关键。目录结构大概长这样~/.cli-anything/ ├── cli # 入口脚本 ├── config.env # 全局配置文件 └── commands/ ├── file-rename.sh # 批量重命名 ├── file-dedup.sh # 文件去重 ├── img-compress.sh # 图片压缩 ├── media-gif.sh # 视频转 GIF ├── media-frames.sh # 视频抽帧 ├── net-health.sh # HTTP 服务健康检查 ├── sys-report.sh # 系统巡检 └── text-extract.sh # 文本提取2.2 入口脚本分发逻辑可以极简入口脚本不需要花哨职责只有一个查表、转发。下面是我实际在用的版本只做三件事——列出命令、校验参数、传递执行#!/usr/bin/env bash # cli-anything 入口脚本 set -euo pipefail COMMAND_DIR${CLI_ANYTHING_DIR:-$HOME/.cli-anything}/commands if [ $# -lt 1 ]; then echo 用法: cli [command] [args...] echo echo 可用命令: for f in $COMMAND_DIR/*.sh; do name$(basename $f .sh) desc$(sed -n s/^# DESC: //p $f 2/dev/null) printf %-18s %s\n $name $desc done exit 1 fi CMD$1 shift SCRIPT$COMMAND_DIR/$CMD.sh if [ ! -x $SCRIPT ]; then echo 错误: 未找到命令 $CMD 2 exit 2 fi exec $SCRIPT $这里有几个关键决策值得说明。set -euo pipefail是必须的它让脚本在遇到未定义变量、管道中途失败时立刻退出而不是带着错误状态往下跑。exec让子命令取代入口进程这样子命令的退出码会原样传给调用方后续做自动化判断就靠这个。列出命令时只读脚本第一行注释不执行任何代码所以哪怕某个脚本写坏了帮助列表依然能正常显示。2.3 三个约定让脚本间能互相协作只有入口还不够真正的系统需要约定。我给所有子命令立了三条规矩实践下来非常管用。第一每个脚本必须有标准头注释。第一行# DESC:描述功能第二行# USAGE:写用法示例。入口脚本的列表展示、未来的自动补全、同事阅读代码全依赖这两行。第二全局配置统一加载。每个子命令开头固定写一句source ${CLI_CONFIG:-$HOME/.cli-anything/config.env}下载路径、默认压缩质量、要巡检的磁盘列表全部放在config.env里改一处所有命令生效。而不是让每个脚本里散落着魔法数字。第三退出码统一语义。0代表成功1代表业务性失败比如文件不存在2代表参数错误。这跟很多系统工具的习惯一致也方便外层脚本用或||做流程控制。骨架搭建完成之后工具集已经能跑了但真正让它值钱的是里面一个个具体的命令。下面挑五个使用频率最高的模块把实现思路和核心代码拆开讲。3. 五个高频模块的实战拆解3.1 文件整理重命名和去重先演一遍再动手文件操作是所有场景里风险最高的因为一旦误操作数据就没了。所以我的文件类命令永远先支持--dry-run参数只打印将要做什么不真正执行。跑一遍确认无误去掉参数再正式操作。批量重命名脚本的核心逻辑是让用户传入一个通配符模式和一个新文件名模板脚本在末尾自动追加递增序号#!/usr/bin/env bash # DESC: 按规则批量重命名文件支持 --dry-run 预览 # USAGE: cli file-rename [--dry-run] IMG_*.JPG photo-%03d.JPG set -euo pipefail DRY_RUN0 if [ ${1:-} --dry-run ]; then DRY_RUN1 shift fi PATTERN${1:?缺少匹配模式例如 IMG_*.JPG} TEMPLATE${2:?缺少新文件名模板例如 photo-%03d.JPG} count0 shopt -s nullglob for f in $PATTERN; do count$((count 1)) ext${f##*.} # 模板里 %03d 是序号占位符这里用 printf 展开 printf -v new_name $TEMPLATE $count new_name$new_name.$ext if [ $DRY_RUN -eq 1 ]; then echo 将重命名: $f - $new_name else mv $f $new_name fi done echo 共处理 $count 个文件这里shopt -s nullglob很重要没有匹配时for循环不会拿字面量IMG_*.JPG去执行。另外所有变量都加了双引号路径里有空格也不会出问题。文件去重我用的方案是按 SHA-256 哈希分组先找大小相同的文件再计算哈希。这个脚本比重命名长但思路很清晰先用find找出所有文件按(size, hash)分组把重复项列出来默认只打印不删除。实战中这个脚本帮我清理过两次积累多年的网盘同步目录腾出几十 GB 空间。3.2 媒体处理图片压缩和视频抽帧是刚需媒体处理的瑞士军刀就两个图片用 ImageMagick视频用 FFmpeg。这两样工具生态成熟、跨平台、参数稳定而且都能在纯命令行下完成批量操作实在没有理由不开 GUI。比如批量压缩图片到统一宽度同时保持质量。这个场景在我写博客、做 PPT 素材时几乎每周都会用到#!/usr/bin/env bash # DESC: 批量压缩图片限制最大宽度并调整质量 # USAGE: cli img-compress 目录 [宽度] [质量] set -euo pipefail SRC_DIR${1:?请指定图片目录} WIDTH${2:-2000} QUALITY${3:-85} OUT_DIR$SRC_DIR/compressed mkdir -p $OUT_DIR for img in $SRC_DIR/*.{JPG,jpg,PNG,png,webp}; do [ -f $img ] || continue magick $img -resize ${WIDTH}x -quality $QUALITY $OUT_DIR/$(basename ${img%.*}).jpg done-resize ${WIDTH}x里的表示只缩小不放大所以原图本来就小于 2000px 的不会被强行拉伸。{JPG,jpg,PNG,png,webp}是花括号展开能让大小写扩展名一次匹配。加上[ -f $img ] || continue是为了防止某些扩展名没有匹配到文件时for循环拿字面量当成文件名去执行。视频抽帧更简单一条 FFmpeg 命令就能搞定#!/usr/bin/env bash # DESC: 从视频中按间隔抽取帧 # USAGE: cli media-frames video.mp4 间隔秒数 [输出目录] set -euo pipefail VIDEO${1:?请指定视频文件} INTERVAL${2:-2} OUT_DIR${3:-frames} mkdir -p $OUT_DIR ffmpeg -i $VIDEO -vf fps1/$INTERVAL -q:v 2 $OUT_DIR/frame-%04d.jpg这里fps1/$INTERVAL的意思是一秒钟抽取多少帧的反比即每INTERVAL秒取一帧。-q:v 2控制输出 JPEG 质量数值越小质量越高2 已经是视觉无损了。这个脚本我在做视频素材粗筛时极其顺手几百个镜头先全部抽帧出来导入图片管理工具选中的再进剪辑软件精修效率起码翻了一倍。3.3 网络检查HTTP 健康状态和服务就绪等待网络类命令的典型场景是部署完服务后确认接口是否正常写脚本等待某个服务启动完成批量检查一批 URL 的返回状态。这些用curl加系统命令组合起来就是一个小工具箱。服务健康检查是我最常用的一个。它把所有需要关心的接口放在配置文件里命令循环请求并输出状态#!/usr/bin/env bash # DESC: 批量检查 HTTP 接口健康状态 # USAGE: cli net-health [urls-file] set -euo pipefail URLS_FILE${1:-$HOME/.cli-anything/urls.txt} while read -r url; do code$(curl -s -o /dev/null -w %{http_code} --max-time 5 $url) if [ $code 200 ]; then echo OK $code $url else echo FAIL $code $url fi done (grep -v ^# $URLS_FILE)-w %{http_code}让 curl 只输出响应码-o /dev/null丢弃响应体。grep -v ^#过滤掉配置文件里的注释行。配合cron每天跑一次服务出问题那天早上我就能在日志里看到而不是等用户来投诉。另一个高频场景是等待服务就绪。部署完容器或启动一个后台服务通常需要几秒到几十秒才能接受请求。靠人工刷新太焦虑了我在脚本里用了一个组合curl -fsS --retry 20 --retry-delay 3 --retry-connrefused $URL /dev/null echo 服务已就绪--retry-connrefused是在连接被拒绝时仍然继续重试这比单纯重试更适合进程刚刚启动、端口还没监听的阶段。20 次重试、每次间隔 3 秒上限是 60 秒足够覆盖绝大多数服务启动时间。3.4 系统巡检一张表看懂机器今天的状态系统类命令没什么高深技术但胜在把多条常见命令组合成一个格式化输出让巡检从敲五条命令变成敲一条命令。我的sys-report脚本会输出四块内容时间与运行时长、磁盘使用、内存占用、CPU 负载前五的进程。#!/usr/bin/env bash # DESC: 输出系统状态概览 # USAGE: cli sys-report set -euo pipefail echo $(date %F %T) echo [磁盘使用] df -h / | tail -1 | awk {print 根分区: 总 $2 / 已用 $3 / 可用 $4 / 使用率 $5} echo [内存使用] free -h | awk /Mem:/ {print 总内存: $2 / 已用: $3 / 可用: $4} echo [CPU负载 TOP5] ps aux --sort-%cpu | head -6 | awk {printf %-8s %-6s %s\n, $3, $11, $12} echo [负载均值] uptime | sed s/^ *//你可以看到我没有写任何复杂的逻辑纯粹是现成命令的排版集成。但这正是 CLI-Anything 的哲学之一同样的信息换个组织方式可读性就完全不同。我值班时每天扫一眼这个输出比打开系统监视器一个个翻快得多。ps aux --sort-%cpu在 Linux 下按 CPU 使用率降序排列head -6取前五加表头。macOS 上参数略有不同我因为这个踩过坑后面专门写了一节讲跨平台问题。3.5 文本处理JSON 查看和正则提取文本处理是命令行的传统主战场。jq是 JSON 的解析神器grep配合正则做信息抽取更是基础到不能再基础。比如我经常需要从一个较大的 JSON 响应里提取几个字段生成一个紧凑列表#!/usr/bin/env bash # DESC: 从 JSON 文件中提取指定字段并格式化为列表 # USAGE: cli text-extract file.json jq-filter set -euo pipefail FILE${1:?请指定 JSON 文件} FILTER${2:?.jq 查询语法例如 .items[] | .name} jq -r $FILTER $FILE | sed s/^/ /实战例子接口测试时返回了一个包含五十个对象的数组我只关心每个对象的id和status字段于是运行cli text-extract result.json .items[] | \(.id)\t\(.status)输出是制表符分隔的两列可以直接粘贴进表格工具。一次字符串操作比在图形工具里一层层展开 JSON 树快了不知道多少倍。正则提取我也常用尤其是日志分析。下面的脚本从日志文件里筛出异常堆栈并把对应的日期时间一并提取grep -E Exception|ERROR app.log \ | grep -oE ^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}|Exception.*$ \ | paste - -虽然这条命令有点取巧但足以说明模式先粗粒度筛选再用grep -oE把关键片段切割出来。日志百万行也没关系命令行的速度扫一遍也就是几秒钟的事。4. 维护脚本工具集踩过的坑排查链路实录这部分可能是对所有人最有直接参考价值的。工具集用久了脚本多了问题一定不是写不出来而是维护不住。我踩过不少坑挑四个最典型的把完整链路写出来。4.1 命令名冲突我的脚本抢了系统命令的名字某次我在commands/里放了一个file脚本想快速查文件类型。过了两天另一个自动化构建脚本开始出错报错信息指向file命令的执行结果异常。排查了半天才发现系统本身有一个file命令用于识别文件类型而我的commands/file.sh所在目录被放在了PATH的最前面所有调用file的地方都被我的脚本截胡了。排查链路是这样的先看报错日志再手动执行构建脚本里的命令发现file report.pdf输出的是完全不符合预期的字符串。然后我用which file查看实际调用的路径才知道命中了我自己的脚本。继续检查echo $PATH确认了自定义目录排在/usr/bin之前。修复方案一是改名我的所有命令统一加了功能前缀比如file-dedup、file-rename避免和系统命令撞车。二是调整了PATH顺序自定义目录排在最后只有系统找不到的命令才会落到我的工具集。这样做降低了优先级但也完全避免了劫持系统命令的风险。经验教训是新脚本命名前先跑一下which 命令名确认不存在同名系统命令。4.2 静默失败的代价set -euo pipefail不能省早期写脚本时很多子命令没加set -euo pipefail。有一次跑批处理转码中途 FFmpeg 因为一个损坏的源文件报错退出但脚本没有终止继续处理后面的文件。问题是转码输出是覆盖写同一批文件于是所有文件都被截断成了 0 字节。等我发现时源文件已经没了。这个坑的排查链路比定位命令冲突简单得多但代价惨痛。从那以后我立了一个规矩任何脚本第一行有效代码必须是set -euo pipefail。它包含三层保护-e任何命令返回非零状态脚本立即退出。-u使用未定义变量直接报错避免变量拼写错误静默成空字符串。-o pipefail管道中任何一段失败整个管道的退出码就是失败的。我又在这基础上加了一个习惯所有涉及覆盖写、删除、移动的命令必须加--dry-run或先备份。宁可多敲一次回车也不能再赌脚本绝对正确。4.3 路径里有空格变量不加引号的连锁事故有一次写批量处理脚本处理了一批来自 Windows 同事的文件夹命名带空格。脚本里写的是for f in $SRC_DIR/*.pdf; do mv $f $OUT_DIR/ done运行结果惨不忍睹mv把路径按空格拆成了多个参数报错之外部分文件被移动到了错误位置。这个坑几乎没有排查过程一眼就能看出是空格问题但关键是形成条件反射所有变量引用必须加双引号$f而不是$f。从那次之后我审查自己的脚本时只看一条规则出现了不带引号的变量直接判不合格。包括在for循环的单词分割场景如果确实需要按空格分割我会改用while read配合数组而不是靠默认的IFS行为。4.4 跨平台差异同一个脚本换台电脑就崩我的工具集最早是在 Linux 上开发的后来换了 macOS 主力机问题立刻爆发。最典型的是sed -i的差异GNU sed 和 BSD sed 的参数形式完全不同我的脚本里sed -i s/foo/bar/在 Linux 上没问题macOS 上直接报-i需要后缀参数。同样的还有ps aux --sort-%cpu这种 Linux 独有参数。排查链接很简单在 macOS 上逐条执行脚本里的命令定位到sed这一行报错man sed一查发现 BSD 实现要求-i 。短期修复是给 macOS 单独写函数判断长期方案是尽量用perl -pi -e代替sed -i用pgrep/pkill配合ps -Ao这种两边通用的参数。跨平台问题没有一劳永逸的解法我的原则是写脚本时就假设它会在另一台机器上跑尽量用 POSIX 标准命令或者用uname -s做一次分支判断。工具集本身就是给自己用的不能让环境差异成为日常摩擦。5. 从我的脚本进化为可生长的平台工具集跑到某个阶段你会发现瓶颈不再是脚本数量而是维护方式。这个阶段有两件大事要做让新命令能被自动发现让所有命令有统一的帮助与补全。做完这两件事CLI-Anything 才真正从一个脚本集合变成一个可生长的平台。5.1 放下即用动态命令发现机制我在第三节展示的入口脚本里使用了for f in $COMMAND_DIR/*.sh的方式来列出命令这本身就是一种动态发现。你再也不需要在主脚本里维护一份命令清单新增脚本的唯一步骤是把.sh文件放进目录顺手chmod x。这个机制带来的好处远超省事那么简单。它意味着你可以在任意时间、任意状态往系统里补充能力而不需要担心破坏已有部分。模块之间天然隔离一个脚本崩溃不会影响其他命令。我在实际使用中甚至会把某个完整项目里附带的小工具直接复制成一条命令测试好了就用不用就删一点也不心疼。5.2 统一帮助与命令行补全脚本一多记忆负担就会上来。解决记忆问题的唯一可靠方案是让工具自己提供帮助。我的做法很朴素既然每个脚本都有# DESC注释那就用脚本提取它生成完整的帮助页。入口脚本的cli无参数运行时就会打印一份命令列表和描述。命令行补全更进一步。在 Bash 环境下我加了一个简单的补全函数。用户输入cli img-之后敲 Tab菜单自动列出所有img-开头的命令_cli_complete() { local cur${COMP_WORDS[COMP_CWORD]} COMPREPLY( $(compgen -W $(cli --list-commands) -- $cur) ) } complete -F _cli_complete cli--list-commands是入口脚本里的一个隐藏参数只输出命令名列表方便被程序调用。整个补全机制是菜鸟级别但体验提升是实打实的。配了补全之后我使用工具集的心理负担大幅下降不再需要死记硬背命令名了。5.3 配置中心让脚本们用同一套默认值最后一步是把散落的默认值收拢到config.env。我的原则很简单一个脚本里凡是可能想改的值都应该来自环境变量或配置文件而不是写死在代码里。路径、压缩质量、超时时间、重试次数、默认文件名统统归config.env管。# ~/.cli-anything/config.env DOWNLOAD_DIR$HOME/Downloads BACKUP_TARGETS/data /home/www MAX_IMAGE_WIDTH2000 IMAGE_QUALITY85 HTTP_TIMEOUT5 HTTP_RETRY20 HTTP_RETRY_DELAY3子命令开头统一source这个文件缺省值集中在同一个文件里任何人都能一眼看清这套工具预设了什么行为。当你想把工具集分享给同事时只需要复制这个目录、改一下config.env里的路径所有脚本立刻能在对方机器上工作。6. 让团队也用起来安装与分享的实操备忘CLI-Anything 如果只是自己用那价值减半。工具集成熟之后我把它逐步引到了团队协作里。这里有个关键的经验别人越容易部署越可能形成使用习惯。所以我写了一个安装脚本目标是在一台干净机器上从零到跑通不超过两分钟。install.sh做的事情很朴素#!/usr/bin/env bash set -euo pipefail REPO_DIR$HOME/.cli-anything BIN_PATH/usr/local/bin/cli # 1. 把工具目录复制到目标位置 mkdir -p $REPO_DIR/commands cp -r commands/* $REPO_DIR/commands/ [ -f config.env ] cp config.env $REPO_DIR/ # 2. 创建入口符号链接 chmod x $REPO_DIR/cli ln -sf $REPO_DIR/cli $BIN_PATH # 3. 校验 if command -v cli /dev/null 21; then echo 安装完成试试执行: cli else echo 安装失败请检查 PATH exit 1 fi/usr/local/bin需要管理员权限但这是最方便、最不容易出错的方式。如果你不想碰权限也可以把符号链接放到~/.local/bin并在PATH里加上。注意ln -sf的-f会覆盖已有链接多次安装不会报错。校验步骤用command -v而不是which因为command -v是 POSIX 标准更可靠。团队使用还有一个隐藏需求版本管理。如果直接把文件扔给对方哪天你改了脚本对方还在用旧版问题就来了。我的方案是轻量的给工具集打上版本号在入口脚本里维护一个全局变量提供cli --version同时在添加新命令时保留一个changelog文件记录。哪怕不做自动化分发至少出现问题能知道对方在跑哪个版本。安全方面也要提一句从互联网拉取的脚本直接放进commands/并赋予可执行权限其实等于把别人的代码放到你的PATH里运行。我的策略是每一条第三方脚本在放进目录前必须完整读一遍把可疑的操作删掉或改掉。尤其是带rm -rf、curl | bash、远程下载再执行这类模式的脚本要格外警惕。7. 运行机制背后的几个细节索引、参数和输出写到这里一些看着不起眼但用起来很顺手的细节值得单独说。它们决定了工具集一天的体验是顺畅还是别扭。参数解析的演化。早期脚本我直接$1、$2手工取参后来发现参数带默认值、可选参数交错的时候手工判断很快变成一团乱麻。现在统一规则是必填参数用${1:?错误提示}直接拦下可选参数用默认值初始化开关类参数在脚本开头统一解析。这套规则简单到可以不加任何依赖库但已经能覆盖绝大多数脚本场景。输出风格。我后来给所有命令统一了标准机器可读的数据用--json参数输出人看的摘要用文本输出所有错误信息一律送到标准错误2正常输出送到标准输出。这样错误信息不会污染管道数据外层脚本grep结果时不会误匹配到报错行。命令前缀的命名法。命令名统一是对象-动作格式比如img-compress、media-gif、file-dedup、net-health。这样互补关联的命令会自然排序在一起按 Tab 补全时也能看到一组相关能力。开头用file-、media-、net-、sys-、text-这些域前缀而不是想当然地给脚本起个文艺名字长期维护时记忆成本会大幅增加。这些细节单独看都不起眼但它们让工具集从一堆能用但互不兼容的脚本走向一个结构稳定的系统。现在这套 CLI-Anything 已经在我几乎所有日常工作流里扎根了。写博客前批量压缩图片、部署后检查接口状态、每天早上的系统巡检、每周整理一次下载目录全都是敲一条命令的事。我体会最深的一点是它带给我的不只是操作更快而是心态上的变化——遇到重复任务时我不再觉得又要手工操作了而是很自然地想写一条命令把这件事固化下来。这种思路一旦形成你会发现身边几乎所有事情都能被拆解、模板化、自动化而注意力反而被解放出来去做真正需要判断和创造的工作。如果你也要搭一套自己的 CLI-Anything我的建议是从最折磨你的那个重复场景开始只写一个脚本把它放进目录里。先让一条命令跑起来别想着一口气做成大工程。等尝到了一条命令替代十次点击的甜头后面的扩展是完全停不下来的。