ARTICLE DETAIL

资讯详情

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

用Bash打造cua:一个轻量级命令行效率工具箱

用Bash打造cua:一个轻量级命令行效率工具箱 1. 从一条重复了三遍的命令说起事情是这样的。有一段时间我需要频繁处理一批批量导入的素材文件每天要做的事基本一样把一堆命名混乱的IMG_2024xxxx.JPG改成带分类前缀的编号文件顺便清理掉文件夹里的*.tmp临时文件再生成一份简单的清单。最开始我每次都手动敲mv、rename、ls list.txt一天至少要重复五六遍同样的操作。六月底的一个下午我刚把同一串命令粘贴到第三个终端窗口突然有点厌烦了。这些操单别看简单一旦文件数量到几百个手动敲命令就很容易出问题漏改一个、写错一个前缀、路径里带空格导致报错。于是我用一个周末把平时最高频的那几段操作抽了出来封装成一个小工具名字就叫“cua”。全称是Common Utility Actions说白了就是“常用小动作合集”。这个项目最开始只是想给自己省时间后来我在几个朋友之间简单分享过发现大家都有类似的需求不想装那种几百MB的图形化效率工具也不想为了改个文件名去专门学一套复杂脚本只想要一个能快速丢出去用、命令短、长得像Linux原生命令的小工具。如果你也是那种常年要给文件改名、批量归档日志、检查本机环境、重复初始化目录结构的人这篇文章想把cua的思路、实现细节和踩过的坑一起记录下来。它不是那种需要高深技术才能看的项目更像是一个普通开发者的“低科技”效率工具箱每个人都能按自己的习惯复刻一个。2. cua的整体设计思路与取舍2.1 先解决“高频但零碎”的操作而不是做一个“超级工具”我给自己定了一个原则cua只做那些“高频但零碎”的事情。高频的意思是每周至少会遇到一次零碎的意思是单次做起来不复杂但加上判断条件后容易出错。第一批进入这个清单的包括批量重命名、按时间归档日志、清理临时文件、固定目录结构生成、基本环境信息检查。这些操作有一个共同特点它们本来都能用原生Shell命令拼出来但每次都拼一遍太浪费。而且一旦跨到两三天不碰那些参数和写法就记不住了——这正是写脚本最合适的边界。另外我还刻意排除了一些功能。比如同步文件、备份数据库、自动化部署这些属于已经有成熟工具的场景不在cua里重复造轮子。把边界划清楚后项目才会保持小、保持快、保持容易维护。如果一开始就不想清楚这一点工具很容易沦为一个什么都往里塞但什么都做不好的大杂烩。2.2 为什么给这个工具起名叫“cua”名字其实没有太高深的含义就是要短、好打、基本不会和别人撞车。三个字母在终端里敲起来非常顺滑而且不会和常见的cd、cp、mv产生混淆。当初也考虑过叫“toolkit”之类的名字但太长每次敲完就失去了“快”的意义。另外cua在英文里可以自然地展开成“Common Utility Actions”即使别人第一次看到这个命令名也可以从命名上大致猜到它跟“常用工具操作”有关。项目内部的组织方式也像Excel里不同Sheet一样清晰我用子命令来区分工具类别每一个子命令就是一个单一职责的小模块。2.3 技术选型为什么用Bash而不是Python你可能已经猜到了cua本质上就是一组Bash脚本。我确实在初期纠结过用Python写会更灵活比如做字符串处理、解析JSON、调用不同系统的API时会更省力。但最终我还是选择了Bash理由很现实几乎任何Linux服务器、macOS机器都自带Bash不需要额外安装运行时我要处理的绝大多数操作都是操作文件、调用系统命令这正是Bash的长处Bash脚本可以被当作普通命令一样直接调用和原生命令的体验基本无差别我自己的使用场景以开发机和服务器居多没有图形界面需求。如果未来碰到了Bash很难处理的任务比如解析复杂的嵌套JSON我的计划是让具体子命令去调用python3或者jq而不是抛弃Bash重写整个项目。保持一个入口、多种实现灵活并存是这个项目能一直维持轻量级的关键。3. 核心模块与实现细节3.1 子命令结构每个操作都长成一个独立文件cua的目录结构很直接不搞花活。入口文件只做参数分发具体逻辑拆分到独立子命令文件中cua/ ├── bin/ │ └── cua # 入口脚本 ├── lib/ │ ├── rename.sh # 批量改名 │ ├── archive.sh # 日志归档 │ ├── clean.sh # 清理临时文件 │ ├── template.sh # 生成目录模板 │ └── info.sh # 环境信息检查 └── README.md入口文件的核心逻辑是解析第一个参数把它当成子命令名再去加载对应的脚本并执行。比如运行cua rename时实际执行的就是lib/rename.sh。这样做的好处是每个功能都被隔离在独立文件里修改一个功能时不需要担心影响其他部分新增功能也只需要在lib/下新增一个文件。3.2 批量重命名先“试运行”再真正执行批量重命名看起来很简单实际上最容易翻车。我见过太多人写rename时没有先预览就直接执行结果改错了又回退不了。cua的rename子命令有一个强制性的设计任何影响文件的操作都必须带有--apply参数才会真正生效否则只打印预览结果。# 不带 --apply 时只打印将要执行的操作 cua rename *.JPG --prefix vacation_ --dry-run # 确认无误后再真正执行 cua rename *.JPG --prefix vacation_ --apply预览输出长这样[DRY RUN] IMG_001.JPG - vacation_001.JPG [DRY RUN] IMG_002.JPG - vacation_002.JPG这里用到了Bash中的echo加条件判断在--apply参数存在时才真正执行mv。这个“先预览后执行”的思路帮我避免了至少三次大规模文件改名事故。我个人强烈建议任何做批量文件操作的脚本都沿用这个模式尤其是服务器上处理生产日志和用户上传文件时。3.3 日志归档按月份自动归类日志归档是另一类高频操作。以前我经常自己写mkdir -p 2024/07 mv *.log 2024/07/但换了月份就会忘记改目录名。cua思路很直接自动从文件的修改时间中取出月份然后把文件移入logs/YYYY/MM/结构里。cua archive ./logs核心逻辑如下for file in $SOURCE_DIR/*.log; do [ -e $file ] || continue year$(date -r $file %Y) month$(date -r $file %m) dest$TARGET_DIR/$year/$month mkdir -p $dest mv $file $dest/ done注意里面那句[ -e $file ] || continue。这是Bash处理通配无匹配时的一个经典陷阱当目录下没有.log文件时*.log不会被展开而是被当作一个带星号的字面量传给循环。加上存在性判断后遇到无匹配时就直接跳过不会报错。3.4 环境信息检查省去记命令的麻烦这个子命令的动机很朴素我经常要在不同的服务器上排查问题每台机器上有没有安装git、node、ffmpeg版本各是多少每次都要分别敲一次命令确认。cua的info子命令用一行搞定cua info它会依次查出当前系统、内核版本、CPU数量、内存大小、磁盘占用以及一些常用工具是否可用。输出示例System : Ubuntu 22.04.3 LTS Kernel : 5.15.0-92-generic CPU : 8 cores Memory : 15.6 GiB Disk : 42% used (234G / 552G) git : 2.34.1 node : v20.11.0 docker : 27.0.3实现原理也很直白无非就是组合uname、nproc、free、df、command -v等命令。但这个工具用熟了以后你会有一种“这台机器的底细一眼看穿”的踏实感。3.5 目录模板生成从“手动mkdir”到“一键搭骨架”最后一个核心子命令是cua template用来批量创建标准目录。比如一个数据类项目的固定结构往往是data/raw、data/processed、outputs、logs等。每次手动mkdir -p也很烦cua可以按预先配置好的模板一键生成cua template>cd /mnt/data/finance # 先把所有xlsx按业务线加前缀 cua rename *.xlsx --prefix 2024-07_ --dry-run # 确认无误后执行 cua rename *.xlsx --prefix 2024-07_ --apply # 建立归档目录并移入 mkdir -p archive/2024/07 mv 2024-07_*.xlsx archive/2024/07/整个流程用不了十秒而且中间每一步都有反馈和确认机会。以前手动操作时经常是想好了怎么改但敲下去才发现文件名里有空格/中文/特殊字符导致mv直接报错。现在把改名和归档拆开做问题定位就清楚多了。4.2 场景二清理临时文件和定位磁盘占用服务器上磁盘告警几乎是运维日常。告警之后通常需要先登录服务器、看看哪个目录占得多再用du一层层往下查。cua的clean子命令可帮我清理项目运行过程中产生的临时产物同时配合info里的磁盘信息使用。cua info | grep Disk cua clean ./build --dry-run cua clean ./build --applyclean子命令的逻辑是根据运行时指定的后缀默认是*.tmp、*.cache、*.swp列出并删除匹配的临时文件。同样强制要求--dry-run先预览。这个设计看上去有点保守但对删文件的命令来说保守一点没错。还有一种更安全的玩法设置一个环境变量CUA_TRASH_DIR如果它被设置clean子命令就不会直接用rm而是把文件移动到一个“回收站”目录里过一段时间再手动清空。这样即使误删了也还有一次后悔的机会。4.3 场景三新机器上快速初始化常用目录换新电脑或者新申请一台开发机时初期最磨人的一环是目录结构对不上。不同项目散落在不同地方日志路径、输出路径一片混乱。cua让我可以把常用项目的目录结构固化下来git clone https://github.com/yourname/cua cd cua ./install.sh cua template># 错误写法 mv $file $newfile # 正确写法 mv $file $newfile我在cua的所有脚本里都强制使用带引号的写法。这也是为什么建议项目里不要自己手写复杂的文件名处理逻辑直接用现成的、经过充分测试的模块。一个小小的引号问题在最严苛的情况下能引发大范围误操作。5.2 问题二不同机器上Bash版本不一致导致脚本报错macOS自带的Bash是3.2版本Linux上多半是4.x甚至5.x。有些语法在4.x中正常在3.2中却无法识别比如**通配符、mapfile命令等。踩过几次坑后我现在会在脚本开头加版本检查if [ ${BASH_VERSINFO:-0} -lt 4 ]; then echo cua requires Bash 4.0 or newer 2 exit 1 fi如果不行就老老实实改用POSIX兼容的写法避免花时间调试跨系统兼容性问题。5.3 问题三子命令越来越多入口脚本变得臃肿最初入口脚本把所有子命令判断都写在一个文件里大概写到七八个子命令时这个文件已经变成一坨一堆if-elif的怪物。每次新增子命令都要动入口文件非常容易改坏其他功能。后来我重构了一下改成“自动发现子命令”的方式for cmd in $CUA_HOME/lib/*.sh; do name$(basename $cmd .sh) if [ $1 $name ]; then shift source $cmd exit $? fi done echo Unknown command: $1这样新增功能只需要往lib/里丢一个脚本不需要再改入口。整个工具后期加子命令的维护成本大幅下降。这也是我回头看收益最大的一个重构。5.4 实操中遇到的典型报错速查表症状常见原因对策No such file or directory文件名带空格且变量没加引号所有变量加双引号*.log: No such file or directory通配符未匹配到文件被当成字面量循环内加[ -e $file ] || continuecommand not found: cua没有安装到PATH目录或当前shell未重载配置执行export PATH$PATH:/path/to/cua/bin或用install.sh在macOS上报语法错误Bash版本过旧提升版本要求或用兼容写法--apply不生效参数解析顺序错误确保把所有参数先收集再判断是否执行实际动作6. 安装与后续扩展建议如果你也想搭一个自己的cua最简单的安装方式就是把bin/cua拷贝到/usr/local/bin/下同时把lib/目录放到固定位置并在入口脚本里通过CUA_HOME环境变量指定路径。export CUA_HOME$HOME/.cua export PATH$PATH:$CUA_HOME/bin在项目里我额外写了一个install.sh会自动检查当前shell是bash还是zsh把上面两行追加到~/.bashrc或~/.zshrc中。这样换新机器时拉一下仓库、执行安装脚本就能用。关于后续功能我个人有一个实用的扩展方向把cua和~/.ssh/config结合做一个cua remote [hostname]的子命令把常用服务器上的环境信息和日志路径一次打印出来。另外一个方向是给rename子命令增加正则支持让批量改名能处理更复杂的模式。最后一个额外的小技巧在lib/info.sh里加一个“最近未执行cua的天数”字段相当于给自己的效率工具做个“使用监控”。这不是什么严肃功能但它会让你直观感受到一个几十行的脚本确实在帮你省下很多重复劳动。
返回列表