ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CLI-Anything:把重复工作变成一条命令的终端工具箱实战指南

CLI-Anything:把重复工作变成一条命令的终端工具箱实战指南 1. 项目概述CLI-Anything 到底在解决什么问题1.1 什么是 CLI-Anything先说结论CLI-Anything 不是一个官方认证的框架也不是某个大厂的开源项目它是我在工作里逐渐沉淀下来的一套“命令行万能工具箱”思路加工具链。目标很朴素——凡是重复两遍以上的终端操作一律想办法变成一条可复用命令凡是能通过文本接口完成的任务就不去打开图形界面。名字里的 Anything 可以理解为“让命令行去做更多事”但更贴近我实际心态的翻译是“这破事能不能敲个命令就完事”。我最早萌生这个念头是被一次毫无技术含量的体力活逼的。当时要处理几十张产品图先统一改尺寸再转成 WebP 格式最后按命名规则重命名。在图形软件里一张一张点每张至少 40 秒弄到第十张我就开始怀疑人生。后来我把这个流程写成一行循环命令喝口水的功夫全部跑完。那一刻我意识到日常里那些“点来点去”的麻烦其实绝大多数可以用 CL I 解决只是很多人不知道从哪里下口。CLI-Anything 想分享的就是这套“下口”的方法。1.2 为什么是“Anything”CLI 的边界在哪里CLI 能管理的范围比很多人想象中宽得多。文件、图片、音频、视频、表格、数据库、接口、容器、云资源几乎每一种软件都留了命令行入口。只要某个工具存在 CLI 接口它就能被写到脚本里和别的命令组合成流水线。相比之下GUI 的最大问题不是“慢”而是“不留痕”。你在图形界面里鼠标点过的一百个步骤机器记不住下次还得重新点一遍而命令行操作本身就是一段可保存、可回放、可改写的文本这才是它真正值钱的地方。不过我得先把丑话说在前面CLI-Anything 并不等于“用命令行做一切”。我自己剪视频、调复杂设计稿、比对两版海报的视觉细节时照样会打开图形软件。它的边界很清楚——适合有规则、能枚举、讲确定性的任务不适合需要即时审美判断的工作。比如“把这张图调得更高级”这种事你没法靠一行命令解决但“把文件夹里所有 PNG 统一压缩到 200KB 以下”就是标准的 CLI 主场。认清楚这条边界才不会把自己逼疯。2. 方案选型搭一个可靠 CLI 工具箱的底层逻辑2.1 三大设计原则文本即接口、单一职责、可重复执行CLI-Anything 的核心不是某个神仙工具而是一套选型和组织原则。我实际用下来最重要的就是下面三条缺一不可。第一文本即接口。命令行世界里几乎所有程序都在“读文本、吐文本”。JSON、CSV、纯文本日志、目录列表本质上都是文本流。这带来一个巨大的好处——任何两个工具只要能处理文本就能通过管道组合在一起不需要专门的插件协议。比如curl拿回来一段 JSON我可以直接把它塞给jq解析再经过sort和uniq做统计curl -s ... | jq .items[].name | sort | uniq -c | sort -rn。每一条命令只做一件事但串起来就能完成相当复杂的数据加工。这也是我在下面所有案例里反复使用的方式。第二单一职责。我要求工具箱里的每个命令只解决一个问题。ls只管列目录grep只管过滤文本awk只管按列处理。这样组合起来的时候规则是清晰的。如果你把一个命令写得过重比如“我这条脚本既能压缩图片又能重命名还能生成报告”那它很快就会变成没人敢碰的黑洞。正确的做法是把功能拆成独立小命令再在上一层用函数或脚本编排。第三可重复执行。一个合格的工具命令应当是幂等的跑一遍和跑十遍结果一致。比如创建目录要用mkdir -p下载文件要考虑断点续传写入操作之前先检查目标是否存在。我见过不少脚本第一次跑得好好的第二次直接报错就是因为忘了这一条。幂等性意味着你可以放心地把命令交给定时任务或者在新环境里反复执行。2.2 工具选型我用哪些命令当地基具体到工具选择我的标准很直接优先单文件依赖、几乎零配置、跨平台体验一致。能用系统自带的就不装额外工具需要装的时候尽量选社区公认的“标准答案”。下面是当前工具箱里最常用的几个列了一张表方便对照。工具用途为什么选它替代品zsh交互式 Shell补全和主题生态好兼容 bash 语法bash、fishfzf模糊搜索与选择进入任意目录、挑选历史命令都非常快pecoripgrep (rg)在大目录里搜代码性能极强自动尊重 .gitignoregrep、agfd查找文件语法直观默认行为比 find 友好findjq解析 JSON终端处理 JSON 的事实标准yq、daselbat预览代码语法高亮配合 git diff 很好用cattmux会话保持远程任务不怕断线窗口随意切分screen我不建议一上来就装一大堆工具那样只会让终端变得更复杂。最稳的路径是先熟练 zsh ripgrep jq 这三样把日常查询和数据处理的痛点解决掉再根据实际需要引入 fzf、tmux。工具不在多在于你愿意每天用。我在项目里安装工具都以“能解决一个真实问题”为前提装了不用、或者用了记不住都是一种负担。2.3 配置管理别名、函数与 dotfiles工具装好之后为了让它们真正成为“工具箱”你需要一套整洁的配置。我管理配置的载体是 dotfiles 仓库所有 Shell 配置、工具配置都纳入 Git 版本管理。换电脑的时候拉下来执行一条初始化脚本环境就回来了。在 Shell 配置里我会区分两种封装纯别名和带逻辑的函数。别名适合缩写高频命令没什么分支比如alias llls -la、alias ggit。函数则用于需要接收参数或做判断的场景比如我常写的mcd()创建目录并立刻进入function mcd() { mkdir -p $1 cd $1 }这里必须用函数而不是alias mcdmkdir -p $1 cd $1因为别名展开时不支持灵活的参数引用而且一旦逻辑复杂起来会非常别扭。另外我给自己的配置定了一条规矩别名命名要短、可预测不要拿缩写故意炫技。g代表 gitpy代表 python一看就懂弄一堆只有自己知道的“暗号”三个月后你自己也忘了。3. 实操过程搭建 CLI-Anything 的完整步骤3.1 从零准备终端环境我先说明一下环境选择。macOS 和 Linux 都是默认亲近 CLI 的体验最顺Windows 用户我建议直接用 WSL而不是在 PowerShell 里强行模拟。不是 PowerShell 不好而是大多数 CLI 生态里的工具都优先支持 Unix 风格环境在 WSL 里踩坑最少。包管理器方面macOS 用 HomebrewDebian/Ubuntu 用 aptWSL 同样可以用 apt。下面是在 macOS 上安装基础工具链的完整命令Ubuntu 系把brew install换成sudo apt install即可个别包名会略有差异。brew install zsh fzf ripgrep fd jq bat tmux装完以后做两件事把 fzf 的交互式补全接进 Shell用$(brew --prefix)/opt/fzf/install这个命令按提示操作然后检查常用命令是否在 PATH 里比如执行rg --version、jq --version。这一步很多人不重视结果写脚本时总是command not found其实大多数都是安装后没重新加载 Shell 配置导致的。这里额外说一句终端环境的配置应该“克制”。我不建议一开始就搞一堆主题插件。先保证命令能用、配置能同步再慢慢折腾外观。我自己在稳定使用之后才加了主题和字体但核心永远是能用和可复现。3.2 先把基础设施函数写出来环境准备好之后我建议先做三件事写一个通用的进入目录函数、一个查看 JSON 的函数、一个本地快速搜索的函数。这三样覆盖了我在终端里最高频的操作。通用函数我上面提过mcd不再重复。JSON 查看函数我习惯命名为jsonv它会读取传入的文件或标准输入格式化并高亮输出。借助bat可以做到function jsonv() { if [ -p /dev/stdin ]; then jq . | bat --languagejson --pagingnever else jq . $1 | bat --languagejson --pagingnever fi }这个函数的关键在于兼容“文件输入”和“管道输入”两种模式。-p /dev/stdin检测到标准输入是管道就说明前一个命令正在往这里传数据。这样你既能jsonv data.json也能curl ... | jsonv。本地快速搜索我封装的是 rg 和 fzf 的组合。命令行搜索最头疼的问题是你记得关键词但不记得它在哪个文件里这个函数能直接给出匹配文件列表并且用 fzf 让你交互选择function qsearch() { rg -l $1 . | fzf --preview bat --coloralways {} }这里rg -l只输出包含关键词的文件名fzf 的预览窗口再渲染内容。我当初把这两个工具组合在一起时最大的感悟是CLI 并不等于“冷冰冰的纯文本”fzf 这种模糊匹配交互让几何化的不确定性操作一下子友好了起来。3.3 案例把批量重命名和图片压缩做成一条命令批量重命名是 CLI-Anything 最典型的应用。我经常拿到一堆相机导出的照片命名是IMG_1234.JPG但归档规则要求带日期前缀和连字符格式。传统做法是一个一个重命名而命令行只需要一个循环。先看一个纯 Shell 的版本适合简单替换function batch-rename() { local from$1 to$2 for file in *; do [[ -f $file ]] || continue local newname${file/$from/$to} if [[ $newname ! $file ]]; then mv -n $file $newname fi done }注意这里有两个容易踩的细节。第一[[ -f $file ]] || continue是为了防止通配符没匹配到文件时把字面量*当作文件名处理。第二mv -n表示不覆盖已存在的文件这是我在一次险情后加的习惯——本来想重命名结果不小心把同名文件覆盖了数据丢得很冤枉。更复杂的重命名规则建议用 Perl 风格的rename命令它支持完整正则比如rename s/IMG_(\d{4})\.JPG$/IMG-$1.jpg/ *.JPG其中$1代表正则里第一个括号捕获的四个数字。图片压缩我单独写了一个脚本参数设计要解释一下。以 JPEG 为例常用sipsmacOS 自带或 Python 的 Pillow 库。我这里用 Pillow 举例跨平台性更好#!/usr/bin/env python3 from PIL import Image import sys, os for path in sys.argv[1:]: img Image.open(path) img.save(path, quality80, optimizeTrue) new_size os.path.getsize(path) / 1024 print(f{path}: {new_size:.0f}KB)质量参数 80 不是随便写的。我测试过一组实拍照片质量 80 到 85 之间人眼几乎感知不到细节差异但文件体积能下降 40% 到 60%。压到 70 以下水面、天空这类渐变区域就容易出现肉眼可见的色块。所以脚本默认 80需要极致压缩时再手动调低。这个脚本适合批量压图场景比如给网页准备素材。3.4 案例日志分析和接口调试日志分析是 CLI 命令组合最见功力的地方。假设你有一个 Nginx 访问日志access.log想知道哪些 IP 访问最频繁一条管道就能出结果awk {print $1} access.log | sort | uniq -c | sort -rn | head -20这条命令拆开看非常清晰awk 取第一列 IPsort 让相同 IP 聚在一起uniq -c 统计出现次数sort -rn 按次数倒序head -20 只取前二十名。这就是文本即接口原则的体现四个工具各管一段组合起来完成一次统计。如果想看某个时间段内的接口状态码分布可以加一层时间过滤。Nginx 日志里时间字段是[01/Jan/2025:13:22:10 0000]这样的格式awk 直接做字符串比较即可awk $4 [01/Jan/2025:00:00:00 $4 [01/Jan/2025:23:59:59 {print $9} access.log | sort | uniq -c再说接口调试。我日常要不断调用内部服务接口每次都敲一长串 curl 实在低效。我封装了一个api函数把公共的地址前缀、认证头和超时参数集中管理function api() { local path$1 local base${API_BASE:-https://api.example.com} curl -sS --connect-timeout 5 --max-time 30 \ -H Authorization: Bearer ${API_TOKEN} \ ${base}/${path} | jq . }--max-time 30这个参数是我吃过亏之后加上的。有一次接口卡住curl 默认会一直等下去我以为脚本挂了回头一查是服务端连接没释放。给所有网络请求设一个上限是 CLI 工具箱里最该有的安全习惯。API_TOKEN放在环境变量里而不是写死在函数里避免把密钥随配置仓库一起泄露。4. 常见问题与排查技巧实录4.1 高频报错速查表我在使用和帮同事排查的过程中整理了一张出现频率最高的报错和解决对照表建议直接收藏。现象常见原因解决办法command not found工具没装或当前 Shell 没重载执行which 工具名确认再source ~/.zshrcjq: parse error输入不是合法 JSON或带 BOM先head -c 100 目标文件看内容用jq -R处理前先过滤脏数据命令循环处理文件名出错文件名里有空格或特殊字符循环里用find ... -print0配合xargs -0脚本在 Windows 上报错换行符是 CRLF用dos2unix转换或在编辑器里统一为 LFalias 在脚本里不生效非交互式 Shell 默认不展开 alias脚本内改用完整命令或函数Permission denied脚本没有执行权限chmod x 脚本名4.2 我踩过的几个坑先说“无意义 cat”。很多人写管道喜欢cat file | grep foo这当然能跑但其实是多此一举而且grep可以直接读取文件名参数。更重要的原因在于无限制使用 cat 会让管道更脆弱尤其是目标文件非常大时白白多读一次。我现在的习惯是能直接指定文件就给命令传参需要严格从标准输入读时用grep foo file。另一个大坑是在循环里使用for i in $(ls)。这条命令看上去人畜无害但碰到文件名带空格就会碎成两段带通配符时还可能被解释成多个词。更稳的写法是用 Shell 原生通配符for file in *再加上文件存在性判断。如果你非要用find的结果去循环请记得-print0和xargs -0这对组合它们用空字符而不是换行符分隔文件名专门对抗空格问题。最后是脚本的兜底参数。我在写任何超过十行的 Shell 脚本时第一行都会加set -euo pipefail。这三个参数的逻辑-e让脚本在遇到错误时立即退出而不是带着错误的中间状态继续跑-u把未定义变量的引用直接判为错误pipefail让管道中任意一环失败都导致整条管道返回失败。很多脚本“看起来没报错但结果是错的”都是因为默认行为太宽容了。加了这套参数fail fast问题才能尽早暴露。5. 进阶让 CLI 真正逼近“Anything”5.1 在终端接入大模型能力CLI 工具箱进化到现在最值得提的一件事是它可以接上大模型能力。让终端不再只处理确定性的文本规则还能完成“看懂需求再转换格式”这类模糊任务。比如我封装了一个ai函数思路是把当前命令输出的文本作为问题喂给大模型接口让它返回处理结果或解释。function ai() { local prompt$* curl -sS https://api.openai.com/v1/chat/completions \ -H Authorization: Bearer ${OPENAI_API_KEY} \ -H Content-Type: application/json \ -d $(jq -n --arg p $prompt \ {model:gpt-4o-mini, messages:[{role:user, content:$p}], temperature:0.3}) | jq -r .choices[0].message.content }这个函数最妙的用法不是单独提问而是和管道组合。比如cat error.log | ai 帮我总结这条报错的原因和解决办法或者ls *.json | head -20 | ai 用一句话说明这些文件名的时间规律。说白了大模型的输入输出都是文本天然适合嵌进 CLI 管道。唯一要注意的是别把敏感数据直接发给第三方 API内部敏感信息请务必确认服务商的数据协议。用OPENAI_API_KEY环境变量保存密钥而不是写死也是我一直坚持的安全底线。5.2 用定时任务把命令变成自动化CLI-Anything 的另一个进阶方向是定时自动化。有些命令适合手动敲但像每天生成报表、每周清理临时文件这类任务更适合用 cron 自动跑。这里给你一个标准模板30 9 * * 1 cd /home/user/report ./generate_report.sh /var/log/report.log 21这一行的含义是每周一上午九点半执行指定脚本标准输出和错误输出都追加到日志文件。很多人会忽略21结果脚本悄悄报错了你完全不知道。日志是定时任务的命根子没有日志就等于把命令扔进黑洞。定时任务还有个容易忽略的坑并发重入。如果脚本运行时间超过周期间隔上一次还没跑完下一次又开始了很容易产生脏数据。我会在脚本开头用一个锁文件挡一下exec 9/tmp/my_task.lock flock -n 9 || exit 1flock的作用是尝试获取文件锁如果拿不到就说明上一个实例还在跑当前实例直接退出。这个技巧在报表生成、数据同步这类任务里非常实用能帮你省掉很多“为什么数据对不上”的排查时间。6. 写在最后的经验体会做完这一整套 CLI-Anything 之后我最大的体会是它的价值不在于某一个工具多好用而在于你愿意把“重复劳动”当成一个值得消灭的问题。以前我对着一堆文件手动折腾总觉得是自己懒现在我会想这不是懒这是没有合适的抽象。命令行给了我一种低成本的抽象方式让我把烦琐变成可以复用的资产。如果你也想开始搭建自己的 CLI 工具箱我的建议非常简单不用急着把这里提到的所有工具都装上。先挑一个最近把你烦到的重复任务试着用几条命令拼出解决方案成功了再固化成一个函数。哪怕一开始只有三五个函数它们也会在未来每一次重复操作时替你节省时间。我今天还在保持这个习惯——每当在终端里第二次做同一件事我就会停下来想一想是不是该给这件事写一条命令了。这也算是我把 CLI-Anything 真正落地的最高原则别让自己做机器的活。
返回列表