终端快捷指令封装:alias、Shell函数与独立脚本实践 敲了六七年终端我机器上的~/.bashrc早就不是一份配置文件了更像一本流水账从最早的三五行alias llls -alF到现在两百多行的函数库加十几个独立脚本。让我下决心系统性整理这件事的起因特别朴素——有次做数据核对同一条由七段命令拼起来的管道我连着敲了三十多遍从第四遍开始基本靠方向键翻历史记录中间还漏过一个--exclude参数导致结果偏了。那次之后我给自己定了规矩任何一条命令只要一周内敲到第三次就必须把它变成快捷指令。自己编写终端快捷指令说白了就是把手上的重复劳动固化成一条有名字、能带参数、出错会提醒的命令让终端从一个“输入框”变成你自己的工具台。这件事的门槛比很多人想的低得多写一个能用的 alias 只需要一行写一个带参数校验的函数也就二三十行但它能省下的时间、能避免的手滑事故是复利式的。这篇内容适合三类人天天和终端打交道但还没开始封装命令的开发者、已经写了一堆别名却发现越管越乱的人以及想把团队里那些口口相传的“祖传命令”沉淀下来的维护者。下面我按环境摸底、alias、函数、独立脚本、排错、长期维护的顺序把这几年的实操细节和踩过的坑一次说清楚。1. 先算清这笔账为什么值得自己写快捷指令1.1 重复输入的真实成本与“三次法则”很多人对快捷指令的想象停留在“少敲几个字符”其实字符数是最不重要的一项。一条长命令的成本分三层第一层是按键时间第二层是回忆和检查参数的时间第三层是出错后的返工时间。以我那条七段管道为例按键大概 12 秒但从脑子里调出参数顺序、确认路径拼写正确、检查有没有多打一个空格平均要花 40 秒以上一旦某个参数写错跑完发现结果不对返工成本直接按分钟计。我自己的判断标准就是“三次法则”同一条命令一周内出现第三次立刻封装。这个阈值不是拍脑袋定的因为写一个函数并测试通过通常需要 10 到 20 分钟如果一条命令一周只出现一次一年也就五十次省下的时间还不够调试函数的。反过来一条每天都要用的命令封装投入当天就能回本。这里还有一个容易被忽略的收益封装本身就是一次文档化。你把那条命令写进函数的那一刻参数含义、默认路径、边界条件都必须想清楚这比在笔记里贴一段命令强得多。半年后你忘了当时为什么加那个-maxdepth 3函数注释里写得明明白白。1.2 三种形态选型alias、函数、独立脚本新手最容易犯的错是不管什么需求都往~/.bashrc里塞 alias塞到后来自己都分不清哪个是哪个。我一般按“是否需要参数”和“是否需要被其他脚本调用”两个维度来选形态具体对照如下。形态适用场景是否支持参数是否可被脚本调用维护成本alias固定参数的简写、给常用选项起个短名不支持只能在末尾追加默认不继承需额外配置极低Shell 函数带固定流程、需要参数与判断的日常操作支持需在同一 shell 环境内中等独立脚本需要版本管理、需要给同事用、逻辑超过 50 行支持可做完整参数解析支持较高但最可控选型的核心逻辑是看这条指令的“生命周期”。临时性的简写alias 足够稳定复用的流程函数更合适需要被定时任务、其他脚本或者同事调用的必须做成独立脚本放到 PATH 里。我见过有人把三百行的逻辑写成一个 shell 函数塞在 rc 文件里结果换个终端就找不到排查问题时完全没法单独运行调试这就是形态选错了。还有一个折中做法值得推荐函数体只有几行、调用一个独立脚本。这样入口在 rc 文件里逻辑在脚本里既能随手用又能单独测试。1.3 边界哪些事不该封装成指令有几类东西我明确不建议封装这是踩过坑之后定下来的破坏性操作。比如rm -rf的各种变体、批量替换、强制清理目录。这类命令一旦封装成短名字误触概率会急剧上升短名字反而降低了你的警觉性。需要交互确认的操作。凡是需要你看着输出做判断的步骤封装成一条命令会把你从流程里摘出去失去观察机会。一次性任务。跑一次就再也不用的命令写脚本的时间远超收益还占用了你的命名空间。涉及凭据的操作。把账号、密钥相关的东西写进脚本或历史记录是很典型的坏习惯这类操作宁可手动敲也不要留下痕迹。提示判断标准可以简化成一句话——这条命令出错时后果是“重跑一次”还是“数据没了”。前者随便封装后者一律手动。2. 环境摸底把地基打对的四个动作2.1 先确认自己站在哪个 shell 里这一步跳过去的人特别多然后就会遇到“明明写了却不生效”的经典问题。确认方法很简单echo $SHELL ps -p $$ -o comm第一行输出的是登录 shell第二行才是当前这个终端进程实际在跑的 shell两者不一致是常态。比如系统默认可能是/bin/bash但你打开的终端被配置成启动了zsh那你写进~/.bashrc的东西一条都不会执行。不同 shell 的加载文件也不一样bash 是~/.bashrc交互式非登录和~/.bash_profile登录zsh 是~/.zshrc另外还有~/.profile这种兜底文件。我踩过一次坑在服务器上把函数写进~/.bashrc本地测试一切正常登录到跳板机上执行时全部失效——因为那个环境的登录 shell 走的是~/.bash_profile而它压根没有 source~/.bashrc。稳妥的做法是在~/.bash_profile里加一段判断登录时主动加载 rc 文件if [ -f $HOME/.bashrc ]; then . $HOME/.bashrc fi这样不管从哪条路径进来函数和别名都能用上。2.2 文件分层rc 文件只做加载不做实现我早期把所有东西堆在一个文件里后来那文件长到八百多行改一个别名要滚三屏。现在的结构是这样~/.config/shell/ ├── 00-env.sh # 环境变量、PATH ├── 10-alias.sh # 别名 ├── 20-func-core.sh # 通用函数 ├── 30-func-work.sh # 工作流相关函数 └── 90-local.sh # 机器特有配置不进版本库然后在~/.bashrc末尾统一加载for f in $HOME/.config/shell/*.sh; do [ -r $f ] . $f done用数字前缀的好处是加载顺序确定PATH一定在需要它的函数之前准备好。90-local.sh单独拎出来是为了区分“跟着我走的配置”和“只属于这台机器的配置”前者进版本库后者不进换机器时不会把别人的路径带过去。加载顺序这件事有个具体后果如果你的某个函数内部调用了自定义的命令而那条命令定义在序号更大的文件里启动后第一次调用就会报“command not found”。所以依赖关系要从编号小的往编号大的方向走不要反向依赖。2.3 命名规范与冲突自检命名冲突是隐形杀手。我定的规则是自建指令一律用两到四个字母的短前缀加连字符比如p-、f-、x-避免和系统命令、包管理器装的东西撞名。写完第一件事就是查一查有没有被占用type -a f-find 2/dev/null || echo 可用type -a会把所有同名定义都列出来——包括别名、函数、内建命令和 PATH 里的可执行文件。这个命令我几乎每天都会用一次比which靠谱因为which只查 PATH看不到函数和别名。另外两条硬规矩一是绝不给已有命令加“更危险”的默认参数比如有人喜欢把rm别名成rm -rf这是拿数据开玩笑二是不要用单个字母g、l、s这类名字迟早会撞上某个工具而且你自己半年后也想不起来它干什么。2.4 备份与回滚改 rc 文件之前先备份这个习惯救过我至少三次。最省事的写法是在加载脚本里加一行时间戳备份cp -a $HOME/.bashrc $HOME/.bashrc.bak.$(date %Y%m%d%H%M)看起来有点啰嗦但真出问题时一条cp命令就能回到上一个可用状态。还有一种更隐蔽的故障某个函数里有语法错误导致整个 rc 文件加载中断后面的配置全部失效终端看起来“正常”但什么都没生效。这种情况用bash -n做静态检查就能提前发现bash -n $HOME/.config/shell/*.sh注意bash -n只检查语法不检查逻辑。变量拼错、命令不存在这类问题它发现不了还是得实际跑一遍。3. alias最轻的一层也是最容易翻车的一层3.1 alias 的三条硬限制alias 用起来最爽但有三个限制必须提前知道否则会在错误的地方反复尝试。第一alias 不支持参数位置替换。你没法写alias foocmd $1 --flag这种参数只会被追加到命令末尾。所以凡是要把参数插在中间的需求alias 一律无能为力必须换函数。第二alias 在非交互式 shell 里默认不展开。也就是说你写了个alias buildmake -j8然后在另一个脚本里调用build会直接报找不到命令除非那个脚本里显式shopt -s expand_aliases。第三alias 的展开是文本替换不理解上下文。这意味着如果你把一个命令别名成一个更复杂的管道在某些引用场景下展开结果可能和你预期不符尤其是涉及引号和通配符的时候。理解了这三点选型就不会错固定参数、无参数处理、只用于交互场景的用 alias其余一律用函数。3.2 一组高频 alias 清单下面这些是我用了很多年、几乎没改过的别名覆盖日常最高频的操作。注意每一条后面都加了注释这也是给自己留的说明书。# 列出目录隐藏文件、人类可读、按时间倒序 alias llls -alFh --time-stylelong-iso # 磁盘占用只显示当前目录第一层 alias dusdu -sh -- * 2/dev/null | sort -h # 快速回到项目根目录配合自己的目录结构 alias projcd $HOME/work # 看最近修改过的十个文件 alias recentls -lt | head -n 10每一条我都刻意保留了“可读”的一面ll用了-h让人类可读dus加了2/dev/null屏蔽权限报错recent只取十行避免刷屏。加这些细节的原因很实际——终端输出一旦刷满一屏你后面的操作就会被打断得先往上翻。我见过不少人把别名定义成超长管道一次输出三四十行结果每次用完还得清屏。别名的价值是“快”不是“全”复杂输出交给脚本。3.3 别名覆盖原命令的风险控制有些工具比如某些系统的ls、grep本身就带默认别名你再覆盖很容易出问题。控制风险的办法有两条。一是先看再改。执行type -a ls看看系统已经给它挂了几层如果已经有一层别名我一般会换个新名字比如lsx而不是硬覆盖。二是不要覆盖“有副作用”的命令。cp、mv、rm这三个我从来没覆盖过。有人喜欢把cp别名成cp -i覆盖前询问看着安全实际用久了会形成“总是按 y”的肌肉记忆遇到真正需要谨慎的场景反而更容易出错。如果确实想要更安全的版本用新名字更清楚alias cpicp -iv alias mvimv -iv名字里带i就是提醒自己这是“会问”的版本比悄悄替换原命令好得多。4. 函数真正把流程封起来4.1 函数骨架参数、默认值、退出码一个能长期用的函数骨架基本固定成这四段取参数、设默认、做校验、执行并返回状态。下面是一个模板我几乎所有函数都是从这个结构改出来的f-demo() { local target${1:-.} # 第一个参数缺省为当前目录 local depth${2:-2} # 第二个参数缺省 2 local rc0 if [ ! -d $target ]; then printf f-demo: 目录不存在: %s\n $target 2 return 2 fi # 主逻辑 find $target -maxdepth $depth -type f -printf %s\t%p\n | sort -rn | head -n 20 rc$? return $rc }几个细节值得展开。local一定要加否则函数里的临时变量会污染当前 shell我第一次写函数时就因为漏了local把$path覆盖掉导致后面十分钟所有命令都找不到。${1:-.}这种写法同时处理了“参数没传”和“参数传了空字符串”两种情况比[ -z $1 ]再赋值更紧凑。报错信息输出到标准错误2而不是标准输出这是为了让你能安全地重定向函数输出比如f-demo /data result.txt报错依然会显示在屏幕上。退出码的约定我按常见惯例来0 成功1 一般错误2 参数错误。这样在脚本里可以按需判断if f-demo /data; then echo 成功 else case $? in 2) echo 参数写错了 ;; *) echo 执行失败 ;; esac fi4.2 实战一建目录并进入这是最经典的需求也是最适合做第一个练习的例子。看起来一行mkdir加一行cd就够了但实际写起来有几个细节。mkcd() { if [ $# -eq 0 ]; then printf 用法: mkcd 目录路径\n 2 return 2 fi local dir$1 # 已存在且是目录直接进 if [ -d $dir ]; then cd -- $dir printf 已进入现有目录: %s\n $PWD return 0 fi # 已存在但同名文件拒绝 if [ -e $dir ]; then printf mkcd: 已存在同名文件非目录: %s\n $dir 2 return 1 fi mkdir -p -- $dir cd -- $dir printf 已创建并进入: %s\n $PWD }这段代码里有三个地方是踩坑换来的。--是为了处理以-开头的目录名不加的话像mkcd -test会被当成选项解析。判断“已存在但非目录”这一步很多人省略结果同名文件存在时mkdir报错而cd又失败了用户看到两条错误信息反而不知道哪条是真问题。最后是输出提示明确告诉你当前在哪个绝对路径下避免多标签页操作时搞混。4.3 实战二带条件过滤的文本检索第二个例子稍微复杂一点在一堆项目里找包含某个关键词的文件排除掉构建产物和依赖目录。这种需求我每周至少用五次。f-grep() { if [ $# -lt 1 ]; then printf 用法: f-grep 关键词 [路径默认当前目录]\n 2 return 2 fi local pattern$1 local root${2:-.} if [ ! -d $root ]; then printf f-grep: 路径不可用: %s\n $root 2 return 2 fi grep -rn --binary-fileswithout-match \ --exclude-dir.git \ --exclude-dirnode_modules \ --exclude-dirdist \ --exclude-dirbuild \ --exclude-dir.cache \ -- $pattern $root }--binary-fileswithout-match这个参数很关键不加的话一旦目录里有图片或压缩包输出里会混进“Binary file matches”把有效结果冲散。--同样是保护以-开头的关键词。关于排除目录我的经验是默认排除写死在函数里需要时用参数覆盖。因为这几个目录在任何项目里都是噪音每次手动加--exclude-dir纯属浪费。如果偶尔要搜依赖目录里的东西直接调用系统grep就行这也说明为什么我不建议把函数命名成grep覆盖原命令——你需要保留“原始版本”作为逃生通道。4.4 输入校验与友好报错函数写多了会发现报错质量决定了这个函数你会不会继续用。几条我坚持的标准报错必须包含三样东西——函数名、出错的值、下一步该怎么做。对比一下“参数错误”和“f-grep: 路径不可用: /tmp/nonexist请检查路径是否存在”后者的排查时间接近零。数量判断用$#不要用$1是否为空来判断。因为f-grep 传了空字符串时$#是 1但$1是空的这两种情况语义不同前者应该进入“关键词为空”的专门提示。还有一个细节函数里调用外部命令前先确认它存在。有些环境里rg、fd这类工具没装函数直接调用会报“command not found”用户还以为是自己的用法有问题。if ! command -v rg /dev/null 21; then printf f-grep: 需要 rg请先安装\n 2 return 3 ficommand -v比which更标准在各种 shell 里行为一致。/dev/null 21是为了不把路径打印出来干扰输出。5. 独立脚本需要复用、需要版本管理的时候5.1 脚本头部三件套当逻辑超过五十行或者需要给同事用、需要放进定时任务就该做成独立脚本了。所有脚本我固定用同一套头部#!/usr/bin/env bash set -euo pipefail IFS$\n\t这四行的作用值得逐条说。#!/usr/bin/env bash比#!/bin/bash更可移植因为不同系统里 bash 的位置可能不同env会去 PATH 里找。-e让任何一条命令失败就退出避免错误被后续步骤掩盖-u让引用未定义变量时报错这个能抓出大量拼写错误-o pipefail让管道中任意一段失败都被识别——默认情况下管道只返回最后一段的退出码前面出错了你完全不知道。IFS改成换行和制表符是为了让for f in $(...)这类遍历在文件名带空格时不出错。加上set -euo pipefail之后要留意一个副作用某些命令在“找不到匹配”时返回非零比如grep无匹配返回 1配合-e会导致脚本提前退出。这种地方要显式处理matches$(grep -c pattern file.txt || true)5.2 放进 PATH目录与权限脚本写好后放哪里我推荐$HOME/.local/bin理由是它通常已经在默认 PATH 里多数发行版如此不需要你来改系统目录升级系统时也不会被覆盖。确认方法case :$PATH: in *:$HOME/.local/bin:*) echo 已在 PATH ;; *) echo 需要添加 ;; esac这段判断比echo $PATH | grep更严谨因为它处理了“路径是另一个路径子串”的误判比如/opt/bin和/opt/bin2。添加路径的写法是export PATH$HOME/.local/bin:$PATH注意是加在前面这样你自己写的命令会优先于系统同名命令——这既是好事也是风险所以要严格遵守上一节的命名规范。权限方面chmod 755 ~/.local/bin/f-logscan755而不是777可执行但不可被他人写入。曾经有一次我把脚本目录设成过宽权限结果某次同步时文件名被工具改了大小写排查了半天。5.3 实战带子命令的日志清理小工具下面这个例子把前面几节的东西串起来实现一个带子命令、带参数计算、带安全阀的小工具。需求是扫描日志目录按保留天数算出应该清理的文件先出报告确认后才真删。#!/usr/bin/env bash set -euo pipefail IFS$\n\t LOG_DIR${LOG_DIR:-$HOME/logs} KEEP_DAYS${KEEP_DAYS:-14} SIZE_LIMIT_MB${SIZE_LIMIT_MB:-200} usage() { cat EOF 用法: f-logscan 子命令 [选项] 子命令: scan 只统计不删除 clean 执行删除需配合 --yes 选项: --dir 路径 日志目录默认 $HOME/logs --days 天数 保留天数默认 14 --yes 确认执行删除 EOF }参数计算这一段是重点。假设每天产生约 40MB 日志保留 14 天理论占用约 560MB如果这些日志在写入时做了压缩压缩比按 0.15 估算实际占用约 84MB。设置 200MB 的阈值就是为了留出余量——超过这个值说明要么日志量异常增长要么压缩没生效两种情况都值得先看一眼再清理。calc_estimate() { local dir$1 days$2 local total_mb total_mb$(du -sm $dir 2/dev/null | awk {print $1}) local daily$(( total_mb / days )) [ $daily -lt 1 ] daily1 local projected$(( daily * days )) printf 当前占用: %s MB\n $total_mb printf 按 %s 天估算日均: %s MB\n $days $daily printf 阈值 %s MB%s\n $SIZE_LIMIT_MB \ $( [ $projected -gt $SIZE_LIMIT_MB ] echo 已超出建议清理 || echo 在安全范围 ) }主逻辑用case分发子命令这是命令行工具的通用做法cmd${1:-help} shift || true while [ $# -gt 0 ]; do case $1 in --dir) LOG_DIR$2; shift 2 ;; --days) KEEP_DAYS$2; shift 2 ;; --yes) CONFIRM1; shift ;; *) printf 未知参数: %s\n $1 2; usage; exit 2 ;; esac done case $cmd in scan) calc_estimate $LOG_DIR $KEEP_DAYS ;; clean) if [ ${CONFIRM:-0} -ne 1 ]; then printf 拒绝执行请加 --yes 确认\n 2 exit 1 fi find $LOG_DIR -type f -name *.log -mtime $KEEP_DAYS -print -delete ;; help|*) usage ;; esacfind ... -print -delete这个组合比-delete单独用更安全因为它会先把要删的路径打印出来日志里能查到删了什么。-mtime 14里的加号表示“大于 14 天”不加加号是“正好 14 天”这个区别很多人搞混一定要加。5.4 制表符补全与帮助信息工具做出来之后补全能显著提升使用体验。bash 的补全用complete注册_f-logscan_complete() { local cur${COMP_WORDS[COMP_CWORD]} COMPREPLY( $(compgen -W scan clean help --dir --days --yes -- $cur) ) } complete -F _f-logscan_complete f-logscan这段放在 rc 的加载文件里注意补全函数名以下划线开头避免和你的其他函数撞名。COMPREPLY必须用数组形式赋值直接赋字符串会失效。帮助信息我坚持写在脚本内部就是上面的usage函数而不是单独维护一份文档。原因很简单文档会过期代码里的帮助跟着代码一起改永远同步。另外约定--help和help子命令都输出同一份内容用户怎么猜都能猜对。5.5 破坏性操作的安全阀凡是涉及删除、覆盖、批量修改的脚本我都强制加三道安全阀。第一道是默认干跑。不带确认参数时只打印不执行就像上面的--yes。第二道是目录白名单。清理类脚本启动时先检查目标路径是否在允许范围内避免变量为空导致find /这类灾难case $LOG_DIR in $HOME/*) : ;; *) printf 拒绝操作路径不在允许范围内: %s\n $LOG_DIR 2; exit 1 ;; esac第三道是先数后删。批量删除前先统计数量超过预期上限就中止cnt$(find $LOG_DIR -type f -name *.log -mtime $KEEP_DAYS | wc -l) if [ $cnt -gt 500 ]; then printf 待删文件 %s 个超过安全上限 500已中止\n $cnt 2 exit 1 fi这三道加起来不到二十行但拦住的都是那种“一次操作清空半个目录”的事故。6. 常见问题与排查实录6.1 症状速查表快捷指令出问题症状通常就那几类。我整理了一张对照表遇到问题先按表定位比盲目搜索快得多。症状最可能的原因快速验证方法新终端里命令不存在写进了不会被加载的文件echo $SHELL对比实际加载文件只有当前终端能用新开的失效直接在当前 shell 敲了定义没写进文件新开窗口执行type -a 命令名报“命令未找到”但文件确实在PATH 没包含或无执行权限ls -l看权限command -v找路径执行到一半突然退出开了set -e中间某条命令返回非零临时去掉-e看卡在哪一步输出乱码或缺行IFS被改过或变量未加引号echo $var加引号对照参数里带空格就出错变量引用没加双引号$1替代$1函数里改了变量影响外部漏了local函数内加local后重试6.2 三个真实排查过程第一个案例某同学写了个f-build函数本地能跑在另一台机器上报“找不到make”。直觉以为是make没装command -v make却显示存在。最后发现是 PATH 顺序问题那台机器的 PATH 里有个同名目录排在前面里面放了个名为make的脚本但那个脚本用了不同解释器。排查方法是type -a make看所有命中项一眼就能看到顺序。这个教训让我养成了一个习惯报“找不到命令”时先怀疑 PATH再怀疑安装。type -a和echo $PATH | tr : \n这两条命令能解决八成这类问题。第二个案例一个清理脚本在某台机器上把不该删的文件删了。原因是那台机器的$HOME下有个符号链接指向共享目录而find默认不跟随符号链接但脚本里用了-L参数为了找到链接目标里的文件结果顺着链接跑到共享目录里去了。修复方案是去掉-L并在白名单检查里加上realpath判断真实路径。这个坑很隐蔽因为脚本在测试环境跑得好好的——测试环境没有那个符号链接。第三个案例函数在交互式终端里工作正常放进定时任务就报语法错误。这是因为定时任务用的是/bin/sh而函数用了 bash 特有语法比如[[ ]]、数组。解决办法是脚本头部明确写#!/usr/bin/env bash并且在定时任务配置里显式调用 bash。永远不要假设执行环境是 bash这是我从这个坑里学到的。6.3 跨 shell 兼容的坑如果你的配置要在 bash 和 zsh 之间共用有几个地方必须注意。数组下标不一样bash 数组从 0 开始zsh 默认从 1 开始。共享脚本里尽量不用数组或者改用$传参。echo的行为不同某些 shell 的echo会解释\n转义另一些不会。跨环境一律用printf行为一致。local的支持度local在 bash、zsh、dash 里都有但typeset在某些环境里语义不同。函数内变量一律用local。还有一种情况source和.的区别。bash 里两者等价但某些精简 shell 只支持.。加载配置文件时用.更保险。注意真要跨 shell 共用最稳的办法是把逻辑写成独立脚本rc 文件里只留一行调用。这样解释器由 shebang 决定不受当前 shell 影响。7. 长期维护让这套东西三年后还能用7.1 dotfiles 版本化配置写多了一定会遇到换机器、重装系统、多台设备同步的问题。我现在的做法是把整个~/.config/shell目录放进一个私有版本库克隆到新机器的固定位置然后在 rc 文件里加载。这样做的核心收益不是“同步”而是历史记录。有一次某个函数突然行为异常我完全想不起来改过什么。翻版本库的提交记录发现三周前我为了处理某个临时需求在里面加了一个head -n 5限制输出行数后来忘了删。没有版本记录的话这种问题只能靠一行行读代码。版本化还有一个副作用是逼你写提交信息。写“修复 f-grep 在空目录报错”比写“更新”有价值得多半年后你会感谢当时的自己。7.2 团队共享的边界把自己写的工具分享给同事比自己用要谨慎得多。我的原则是共享的脚本必须能独立运行不能依赖我的个人环境。具体做法是共享脚本里不写死任何个人路径所有路径通过环境变量或参数传入并给出合理的默认值。依赖的外部命令在启动时检查一遍缺什么明确提示。所有输出格式固定不要出现“我的机器上是彩色、你的机器上是乱码”这种事——颜色输出用[ -t 1 ]判断是否在终端里再决定开启。另外一条共享脚本要带一个极简的help说明它解决什么问题、怎么调、有什么前提。很多人分享脚本只发一个文件收的人完全不知道从哪下手。7.3 定期清理快捷指令是会膨胀的。我每季度会花半小时做一次清理动作很简单# 列出所有自定义函数 declare -F | awk {print $3} | sort # 列出所有别名 alias | sort对照着看把三个月没用过的删掉把功能重复的合并。判断“没用过”有个客观依据在执行历史里搜一下。grep -c f-grep ~/.bash_history数量为 0 的基本可以删了。清掉的空位腾出来下次写新函数时更容易想名字也减少了撞名的概率。这个过程看着麻烦实际半小时能搞定比日后花两小时排查“这个命令到底是干什么的”划算得多。8. 我个人用了几年之后的一点体会写了这么多年最大的感受是快捷指令的价值不在“省时间”而在“降低出错概率”。省下的那几秒钟其实感知不强但把一条易错的十六段命令固化成一个名字之后那种“闭着眼睛敲也不会错”的安心感是持续的。我现在回头看最早写的那些 alias有些已经删了有些连注释都没改过一直在用区别就在于当初有没有认真想过它的边界。如果你刚开始做这件事建议从最小的一步入手今天找一个你这周敲了三次以上的命令用 alias 或五行以内的函数把它封起来加上local、加上参数校验、加上报错提示。用一周你会发现它开始改变你敲命令的方式——你会自然而然地想“这个动作以后要不要复用”而这个念头一旦形成剩下的都是水到渠成的事。