☰
CLI-Anything:用命令行工具终结重复操作,打造高效自动化工作流
2026/9/28 17:10:24 网站建设 项目流程

说实话,最开始我给自己搭这套叫 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_RUN=0 if [ "${1:-}" = "--dry-run" ]; then DRY_RUN=1 shift fi PATTERN="${1:?缺少匹配模式,例如 IMG_*.JPG}" TEMPLATE="${2:?缺少新文件名模板,例如 photo-%03d.JPG}" count=0 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 "fps=1/$INTERVAL" -q:v 2 "$OUT_DIR/frame-%04d.jpg"

这里fps=1/$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_WIDTH=2000 IMAGE_QUALITY=85 HTTP_TIMEOUT=5 HTTP_RETRY=20 HTTP_RETRY_DELAY=3

子命令开头统一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 2>&1; 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,我的建议是从最折磨你的那个重复场景开始,只写一个脚本,把它放进目录里。先让一条命令跑起来,别想着一口气做成大工程。等尝到了"一条命令替代十次点击"的甜头,后面的扩展是完全停不下来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询