打造自己的命令行工具箱:Shell+Python自动化实践 作为一个靠键盘吃饭的人我每天做得最多的事情其实不是写新代码而是反复执行那些早就做过一百遍的老活儿批量重命名文件、把 CSV 转成 JSON、同步十几个 git 仓库、清理日志里的重复行、按目录归档素材。干这种活的挫败感在于每一件都很简单但加起来能无声无息吃掉一个上午。与其继续当人肉脚本机我干脆从一年多前开始把所有高频动作固化成了一套命令行工具箱项目代号就叫 superpowers。这篇文章不聊宏大架构就把它从 0 到 1 的完整思路、核心技术选型、六大常用模块以及那些踩到脚疼的坑原原本本拆给你们看。1. 项目缘起当重复劳动开始消磨耐心1.1 那些让人想拍桌子的日常最先让我意识到问题严重的是一个周五的下午。当时我在准备一批素材文件需要把 200 多个IMG_2024*_RAW.JPG改成带日期分组的目录结构顺便把其中一半转成 PNG 缩略图。我用文件管理器手动拖了一个多小时拖到后来手指发酸脑子里只有一个念头这种活儿凭什么要我亲自干类似的场景实在太多了。打开终端一看历史记录全是同一个命令翻来覆去地改参数sed -i批量替换、find找文件、awk切列、git pull循环同步仓库。它们不是不会而是每次都要重新想一遍参数偶尔还会写错而且完全没法保证所有机器上的行为一致。这种会做但不稳定的状态其实比不会做更耗人。1.2 从临时脚本到固定工具一开始我也没想搞什么项目就是在~/bin下面丢一堆.sh文件用到哪个算哪个。但很快问题来了脚本之间互相调用的时候有人用source有人直接拼接变量命名乱成一锅粥换一台电脑之后依赖的jq、ffmpeg到底装没装完全靠赌。真正让我下定决心重构的是有一次我帮同事跑一个文件处理脚本结果因为环境里的python指向的是 Python 2而脚本是按 Python 3 写的直接崩了。那一刻我就明白散装临时脚本永远不可能成为可靠的超能力。superpowers 这个项目要做的不只是把命令收集起来而是给这些琐碎操作一套统一的入口、统一的参数风格、统一的输出格式让它们在任何机器上表现一致。1.3 为什么叫superpowers取名 superpowers想法很朴素一个普通开发者和一个高效开发者之间差的往往不是智商而是一套顺手到不假思索的工具流。就像游戏里的技能按键在特定场景下按一下就能自动完成一整套复杂连招。我们希望把这些高频操作变成肌肉记忆让使用者觉得自己好像突然有了某种超能力——一条命令完成过去要手动操作半小时的事情。这个命名也提醒我一条设计原则命令必须足够短、足够直接短到不需要查文档就敢敲。如果一条命令还需要翻 README 才能想起来怎么用那它就还没达到 superpowers 的标准。2. 核心技术选型为什么选择 Shell 加 Python 的组合2.1 各方案的取舍在设计工具箱时摆在我面前的无非四条路纯 Shell、Python、Go、Node.js。我列过一张表把每个方案的优劣势摊开来看方案上手成本依赖体积跨平台表现最适合的场景纯 Shell低极小一般Windows 上问题较多胶水调用、快速任务Python 3中小好文本处理、文件批量操作、解析数据Go中高单二进制极好需要高性能、需要单文件分发Node.js中大好已有前端技术栈、生态偏 Web对 superpowers 来说项目核心是处理日常文件和文本这些工作对性能并不敏感更重要的是开发效率和移植门槛。Go 做出来的工具确实干净但每次加一个模块都要编译、写样板代码对个人工具库来说有点过度工程。Node 依赖体积大而且我对它的 CLI 参数解析生态一直没有特别顺手。最终我选了Shell 做胶水、Python 做重活的混合方案。2.2 以 Shell 做胶水、以 Python 做重活这个分工背后的逻辑其实很简单Shell 是一门天生的命令编排语言它最擅长的是启动进程、传递参数、检查返回值但一旦遇到复杂的文本解析、JSON 操作、跨平台路径处理Shell 的边界感就变得很模糊写起来容易变成不可维护的天书咒语。所以在 superpowers 里绝大多数超能力入口都是一个 Shell 脚本负责解析参数、检查环境、调用真正的处理逻辑而核心的数据处理逻辑统一用 Python 3 写。比如sp file rename这个命令Shell 层只负责告诉你--dry-run要不要输出预览、目标目录是否存在真正处理 EXIF 信息、重名冲突的逻辑全部在 Python 模块里。这样两边各干各擅长的事代码读起来也清爽很多。2.3 项目结构设计解决了语言选型接着就是目录结构。我最早吃过所有脚本堆在一个目录的亏后来把项目整理成了这样superpowers/ ├── bin/ # 所有子命令的入口脚本集中地 │ ├── sp │ ├── sp-file │ ├── sp-text │ ├── sp-git │ ├── sp-env │ ├── sp-net │ └── sp-doc ├── lib/ # 公用的 Shell 函数库 │ ├── log.sh # 统一日志输出、颜色、级别 │ ├── files.sh # 路径处理、目录初始化 │ └── os.sh # 操作系统检测 ├── modules/ # Python 核心处理模块 │ ├── file_ops.py │ ├── text_tools.py │ └── git_workflow.py ├── config/ # 全局配置 │ └── defaults.yaml └── docs/ └── recipes.md # 常用场景案例统一入口是一个名为sp的符号链接实际上指向bin/sp由它再把任务分发给对应的子命令。这样设计带来的最大好处是使用者只需要记住sp一个开头然后按直觉补充第二个词比如sp file、sp git、sp env几乎不需要查帮助。命令数量多起来之后这种命名一致性的价值会被放得很大。3. superpowers 六大核心模块逐个拆解3.1 sp-file文件批处理与归档文件操作是 superpowers 里最早上线、也最常用的一组命令。核心诉求只有两个批量重命名和按规则归档。批量重命名我做得比较谨慎因为文件一旦改错恢复成本很高。命令设计成了这样sp file rename --dir ./photos --pattern IMG_*.JPG --to vacation_%02d.jpg --dry-run--dry-run是重命名、删除这类危险操作必需的模式它只打印将会发生的动作不真正落盘。输出类似[DRY RUN] ./photos/IMG_1.JPG - ./photos/vacation_01.jpg [DRY RUN] ./photos/IMG_2.JPG - ./photos/vacation_02.jpg [DRY RUN] ./photos/IMG_3.JPG - ./photos/vacation_03.jpg确认无误后去掉--dry-run再跑一遍。所有会覆盖已有文件的操作程序会默认拒绝执行除非显式加--force。这个机制帮我避免了很多次手滑式灾难。sp file archive则是把散落各处的文件按扩展名或日期归入指定目录结构。比如把下载目录里乱七八糟的文档、图片、压缩包各归各的类sp file archive --dir ~/Downloads --by-extension --target ~/Archive背后的逻辑是shutil.move加同名冲突检测Python 的标准库已经足够完成不需要额外引第三方依赖。3.2 sp-text文本转换与清洗如果说文件模块处理的是外部世界的杂乱那文本模块处理的就是内部世界的混乱。日常写博客、写文档、处理数据导出的时候我最常干的三件事是格式转换、批量替换、编码修正。其中使用率最高的是 JSON 与 CSV 互转sp text csv2json --input records.csv --output records.json --pretty这个命令的内核是 Python 的csv.DictReader加json.dump。实际使用时我加了一个很关键的参数--encoding因为从 Excel 导出的 CSV 很有可能是 GBK 编码如果不处理读取出来就是一排乱码。遇到这种情况按下面的顺序先探测、再转换file records.csv sp text iconv --input records.csv --from gbk --to utf-8 --output records_utf8.csv批量替换则参考了sed的用法但做得更保守默认要求用户最前面传一个替换规则文件每行一条旧文本|||新文本避免在命令行里写大量需要转义的特殊字符。命令行太复杂就容易出错把规则放在文件里既好复查也好复用。3.3 sp-gitGit 工作流加速Git 本身已经很强大了superpowers 在它上面做的事情只是把多个仓库的重复操作压缩成一条命令。我最常碰到的场景是同时维护好几个独立项目每天上班第一件事就是把它们全部更新到最新sp git sync --dir ./workspace --remote origin --branch main --depth 50这条命令会遍历指定目录下的所有 git 仓库跳过还没有初始化的目录依次执行fetch、ff-only合并、报告脏工作区。它有个贴心的小设计哪个仓库没有干净工作区就先跳过并明确标注绝不擅自stash或checkout -- .避免把你没提交的改动丢掉。另一条高频命令是提交信息规范化sp git commit-template --type feat --scope user-api --summary 增加登录重试机制它会拼接出一个符合约定式提交规范的 message再让你确认后提交。一开始我觉得多此一举但用了两个月之后回看历史提交记录那种一致的格式感真的会让git log --oneline的可读性提升一个档次。3.4 sp-env环境初始化与配置管理换新电脑、重装系统、给新加入项目的同事配环境这些事看起来都是小概率但每次都要折腾半小时的活。sp-env 要解决的就是让环境初始化尽可能无脑。sp env setup --profile dev这个命令会读取config/defaults.yaml根据当前操作系统自动选择包管理器安装常用工具。比如 Linux 上会优先判断是apt还是dnfmacOS 上则调用brew。安装之前会先把将要执行的安装列表完整打印出来让使用者确认一遍避免一顿操作猛如虎之后才发现装错了东西。这里我踩过一个大坑不要试图在setup里把所有工具都装一遍。工具链膨胀会让初始化时间从五分钟变成四十分钟而且很多工具一辈子都用不上。现在我的做法是拆成profile-core、profile-python、profile-frontend三档按需选择轻量单独跑其中一档只需要两分钟。3.5 sp-net本地网络状态排查这个模块不是为了处理复杂网络架构而是解决一个天天都会遇到的烦心事为什么连不上、哪个端口被占了、内网服务到底起没起。sp net check --host localhost --port 8080 sp net ports --match 8080第一个命令用来确认某个服务端口是否正在监听第二个命令用来定位是哪个进程占了端口。排查端口冲突时它会同时给出 PID 和进程名省去lsof加ps来回组合的麻烦。判断服务健康状态也有一个精简命令可以做简单的 HTTP 状态码检查sp net http-status --url http://127.0.0.1:8080/health --expect 200 --retry 3 --interval 2--expect 200会在连续三次访问中只要有一次返回 200 就判定成功适合用来等待本地服务就绪。写自动化脚本时这个等待逻辑反复用到能省掉不少自己写轮询循环的精力。3.6 sp-doc文档输出与格式规范最后一个模块是为了我写技术文章和接口文档服务的。写文章最痛苦的不是动笔而是格式乱七八糟全角半角标点混用、段落之间多个空行、标题层级跳来跳去。sp doc tidy --input draft.md --output draft_tidy.md这个命令会做以下清理统一中文全角标点、把连续三个以上的空行压成一个、修正常见的中英文之间缺空格问题、检查标题层级是否从一级连续递增。它不改变你的语义只处理排版层面的强迫症需求跑完顺手看一眼 diff 就能直接提交。后来我还给这个模块加了一个接口文档生成小助手能从带注释的 Python 函数定义里抽取参数说明生成 Markdown 表格。诚实说它没有商用 API 文档工具那么智能但胜在零配置、离线可用对内部工具足够友好。4. 真实使用场景一次上线流程里的效率对比4.1 场景设定有一次我负责一个前后端联调功能的上线整个流程可以拆成下面几个阶段更新多个仓库代码、整理配置文件、批量替换测试环境的域名、检查依赖服务状态、最后写部署记录的摘要。放在以前这五个阶段每一阶段都需要打开好几个终端窗口翻历史命令手动校对。4.2 使用 superpowers 之前的操作流老流程大概是这样的# 逐个仓库 cd 进去 git pull有的仓库还要处理本地冲突 cd app-frontend git pull cd ../app-backend git pull cd ../shared-lib git pull # 拷贝配置模板 cp api-example.yml api.yml # 手工把测试域名替换成生产域名打开编辑器逐行改…… # 用浏览器/curl 反复确认服务起来了没 curl -s http://localhost:8080/health ps aux | grep node这些步骤单独看都不难真正折磨人的是上下文切换。每次从一个仓库切到另一个仓库都要重新回忆这个仓库用的是 main 还是 master、它的依赖装好了没有。一套下来半小时起步而且中途特别容易被打断一打断就得从头理思路。4.3 使用后的操作流在 superpowers 的帮助下同一件事变成了# 1. 所有仓库同步 sp git sync --dir ./work --remote origin --branch main # 2. 根据工作目录生成配置文件 sp env fetch-config --service api-gateway --from-template --replace-env prod # 3. 批量替换配置里的域名占位符 sp text replace --input api.yml --rules ./rules_prod.txt --dry-run # 4. 确认依赖服务端口都在线 sp net check --host 127.0.0.1 --port 8080 sp net check --host 127.0.0.1 --port 3306 # 5. 生成提交记录摘要 sp git log-today --dir ./work --format compact每一步都是单条命令每一步的输出都很克制不会刷屏。中间被同事打断十分钟回来扫一眼终端记录就能知道进行到哪一步了完全不用重新理一遍上下文。4.4 时间对比与真实感受同样的流程我专门掐过一次表老方法在完全顺畅的情况下是 28 分钟用 superpowers 首轮跑通是 7 分钟而且其中大部分时间是在等sp git sync的 fetch 网络时间。省的 20 多分钟主要省在不打断和不回忆上。但我也要诚实说这 7 分钟里有 3 分钟是在--dry-run模式下检查替换结果和确认危险操作。这个时间我不会省因为它换的是安全感。工具越强越要在危险动作前保留一道人工确认的闸门。5. 踩坑实录路径、编码、跨平台三个难缠的问题5.1 路径中有空格所有参数都要加引号superpowers 上线后接到的第一个工作事故报告是有个同事执行sp file rename时所有路径带空格的目录全报错了。我排查的时候发现Shell 层解析参数时直接把整个路径拆成了两段传给 Python 之后就出现了找不到文件。这个坑的本质原因其实很经典在 Shell 脚本里变量展开后必须用双引号包起来。比如# 错误示范 python3 module.py --path $dir # 正确示范 python3 module.py --path $dir一句话的事但一旦在循环里写就很容易犯。我后来给 lib 层加了一个参数校验函数任何包含空格或特殊字符的路径都会被单独标记出来并且强制转换为数组参数传递从根上杜绝了这个坑再次出现。5.2 编码混乱UTF-8 的假统一处理文本文件时最让人头皮发麻的不是文件不是 UTF-8而是它看起来是 UTF-8其实不是。很多从旧 Windows 机器上拷过来的文本实际编码是 GBK但文件扩展名照旧是.txt编辑器也能正常打开——因为编辑器默默做了自动探测而 superpowers 里的 Python 可不会自动猜。排查这类问题我推荐用这条链路file my_note.txt # 输出: my_note.txt: ISO-8859 text, with CRLF line terminators看到ISO-8859或non-UTF-8直接转码sp text iconv --input my_note.txt --from gbk --to utf-8 --output my_note_utf8.txt我总结出的经验是所有涉及文本读取的模块默认都先做一次轻量探测发现非法 UTF-8 序列就明确报错并提示用户指定编码而不是自作聪明地猜。猜错了还不如报错。5.3 跨平台兼容不要相信 rm -rf 在 Windows 上的脾气superpowers 最初只在 macOS 和 Linux 上跑直到有一次我在 Windows 的 Git Bash 环境里演示才发现问题远不止路径分隔符那么简单。第一层问题就是/与\。Python 里用pathlib处理路径相对安全但 Shell 层的字符串拼接会直接把 Windows 路径里的\当成转义符吃掉。第二层问题是命令差异Linux 上的rm -rf、cp -r、grep -r在 Git Bash 里多数能用但行为偶尔不一致比如参数顺序、通配符展开规则。我的解决思路是尽量少在 Shell 层处理真实文件只把路径原样传给 Python由pathlib统一处理。同时给 os.sh 加了一层能力抽象比如safe_rm在 Windows 上优先调用 Python 的shutil.rmtree而不是直接rm -rf。这样同一套命令在三个系统上的表现基本一致剩下的小差异再用文档明确标注。6. 如何搭建属于你自己的超能力工具箱6.1 先盘点你的高频动作打造自己的 superpowers第一步不是写代码而是盘点。我最推荐的方法是把过去两三个月的终端历史从头翻一遍统计出现频率最高的命令和参数组合。以 zsh 为例history 1 | awk {print $2} | sort | uniq -c | sort -rn | head -20看到那些反复出现七八次的命令就是你最该固化的超能力候选。比如如果你的历史记录里满是python manage.py migrate、docker compose up那就围绕这些命令包一层更加语义化的封装减少每次敲全命令的负担。6.2 一个工具只需要做好一件事在设计 superpowers 的过程中我最深的体会是模块可以多但每个命令的职责必须单一。sp file rename就只管重命名不要顺手做压缩sp git sync就只管同步不要顺手清理分支。职责一旦交叉命令的参数数量就会爆炸最后变成一个什么都能干但什么都难记的瑞士军刀怪兽。如果你发现自己正在给一个命令加第五个可选参数最合理的操作不是继续加默认值而是拆出一个新命令。6.3 文档即记忆写 README而不是靠大脑再顺手的命令一个月不碰也会生疏。我在项目里强制执行一条规则每个命令必须有对应的 README 段落至少包含一句话说明、常用示例、危险参数警告三部分。模板我固定成这样### sp file rename 一句话说明按规则批量重命名文件。 常用示例 sp file rename --dir ./photos --pattern *.JPG --to vacation_%02d.jpg --dry-run 危险参数警告 --force 会覆盖同名文件使用前必须确认 dry-run 结果。有了文档你就不再依赖记忆。换个电脑、隔三个月回来翻一眼文档就能立刻捡起来。这一点远比任何复杂的自注释代码都重要。6.4 从 v0 到 v1 的迭代节奏最后聊聊迭代节奏。superpowers 不是一天建成的我的第一个版本只有两个命令、几百行代码而且处处是硬编码的绝对路径。但现在回头看这种粗糙的 v0 反而值钱因为它让我立刻进入真实使用场景而不是花一个月设计一个完美但没人用的框架。我的建议是先挑一个你最痛、最频繁的操作做出一个能用的命令立刻放到日常里去用。用两周之后你自然会知道要加什么参数、要改什么输出、要防什么错误。然后带着这些真实反馈再去做第二个命令。这样滚雪球式地迭代会比一次性设计十个模块靠谱得多。我个人在实际操作中还有个习惯每次觉得某个重复动作好烦的时候不硬扛先记到待办清单里等凑够三五个类似痛点再统一设计。这样既不会让自己陷入琐碎工具的泥潭又能保证每一个新模块都来自真实需求。superpowers 这个项目到现在已经陪我走了一年多它最大的价值不是省了多少时间而是让我在动手做事之前心里多了一份这件事我有顺手的招的笃定。