写代码写久了终端会变成第二个家。每天敲的命令里真正需要动脑的其实不到一成剩下九成都是把同一串东西重复输入切换目录、拉取代码、压缩打包、清理缓存、连上测试环境跑一遍脚本。我自己有段时间每天要手敲一条七个参数的命令参数顺序还老记错敲错一次就得重新来那种烦躁感特别消耗状态。后来我把这些动作统统收进终端快捷指令一个短词、一句函数、一个脚本敲下去就完事。这篇文章讲的就是我怎么从零把一套快捷指令体系搭起来包括别名、函数、独立脚本、补全与帮助四层怎么分工配置怎么组织哪些坑我踩过。适合天天泡在命令行里、想把手速换成效率的开发者也适合刚学命令行、总觉得记不住参数的新手跟着做一遍你能得到一套属于自己的、可迁移、可维护的指令库。1. 动手之前先想清楚一条快捷指令该长什么样很多人搭快捷指令的第一步是打开配置文件狂写 alias写了两百行之后发现一半不生效、一半互相覆盖、还有几个把系统命令给顶掉了。问题不在于不努力而在于没有先分层。我在写第一条之前会先问自己三个问题这个动作有多长有没有参数需不需要判断和循环答案不同落点就完全不同。1.1 别名、函数、脚本三层分工与边界最省事的是别名它本质是文本替换把gst替换成git status零成本但它不能接参数、不能做判断只能应付那种完全固定的短命令。第二层是shell 函数它是真正的小程序能接参数、能判断、能循环、能返回退出码适合要带一两个参数逻辑在十行以内的场景。第三层是独立脚本文件它和你的交互式 shell 解耦可以被别的脚本调用、可以单独做版本管理适合逻辑超过二十行、或者需要在非交互环境里跑的动作。我给自己定的判断线很粗暴能别名就别名别名接不住就函数函数超过一屏就拆出去做成脚本。这条线救过我很多次。早期我把一个两百行的备份逻辑塞进.zshrc里的函数结果每次开新终端都要重新解析这一坨启动慢了将近两百毫秒而且改一行就得重开终端。后来拆成bin/下的独立脚本.zshrc里只留一行路径问题全消失。下面这张表是我实际在用的分层判断依据你可以直接照着套特征用别名用函数用独立脚本参数不支持支持支持且可做完整解析逻辑长度一行十行以内不限是否需要条件判断否是是是否被其他脚本调用否偶尔常见启动开销极低低需解析零按需加载适合场景高频短命令带参数组合动作复杂流程、批处理1.2 目录规划为什么我坚持配置和代码分开配置文件和脚本混在一起是灾难的开始。我的做法是在主目录下建一个专门的指令库目录比如~/.mytools内部再分三层aliases.sh只放别名functions.sh只放函数bin/放所有独立脚本。然后在 shell 配置文件里只写两行加载语句把bin/加进PATH。这个结构的好处在于别名和函数是改了就重开终端的轻资产脚本是可以直接传给别人、可以进代码仓库的重资产两者生命周期完全不同混着放会让维护决策变困难。比如你哪天想把某个函数发给同事函数得复制粘贴脚本只要把文件给他就行。另外bin/独立出来后你可以在别的工具里直接调用这些脚本比如编辑器里配一条外部命令、或是定时任务里跑一次完全不需要关心你的 shell 配置。我见过一个反面例子某开发者把所有东西都写进一个两千行的配置文件里还在里面用if判断当前目录是不是某个项目文件夹一旦不在那个目录下整段逻辑就崩。这种配置状态依赖运行环境的写法非常脆一旦换机器、换目录就全废。1.3 命名规范别把自己的指令写进系统命令的坟场快捷指令最容易出的低级事故是名字和系统命令撞车。我给自己定了三条长度两到四个字符优先、加统一前缀、动词在前名词在后。加前缀不是为了好看是为了能一眼看出这是我自己的东西也方便将来批量清理或者临时禁用。比如我会用x开头的命名空间xup表示更新、xlg表示看日志、xzip表示打包。有人喜欢用更短的名字觉得越短越省事但我实测下来两个字母的名字在肌肉记忆里几乎无法区分反而更容易敲错。三到四个字符是一个甜点区既能快速输入又足够区分。还有一条更重要的经验不要覆盖系统已有命令。我早年把一个命令别名成了rm的变体加了自动回收站逻辑结果某次在一个陌生环境里发现rm行为不对排查了快半小时才想起来是自己改的。现在我遇到需要改行为的情况一律起新名字原命令保持原样认知负担小得多。2. 环境盘点加载顺序搞错了写再多也不生效写快捷指令最常见的挫败感不是逻辑写错而是明明写进去了就是不生效。九成情况出在两件事上一是搞错了 shell 类型二是搞错了配置文件的加载时机。这两件事在动手前花五分钟确认能省掉后面好几小时。2.1 先确认你到底在用什么 shell不同 shell 的语法差异不小配置文件名也不一样。确认方法很简单在终端里执行echo $SHELL ps -p $$ -o comm第一条输出的是你登录 shell 的路径第二条输出的是当前这个 shell 进程的实际名字。这两条最好都看因为有些人登录 shell 是一个、但手动切到了另一个这时候你把配置写在登录 shell 的文件里当前窗口是不会生效的。常见的几种bash的交互式配置一般是~/.bashrc登录配置是~/.bash_profile或~/.profilezsh用的是~/.zshrcfish用的是~/.config/fish/config.fish而且语法完全不同别照抄 bash 的写法。我用的是zsh所以后面示例以兼容 POSIX 的写法为主fish用户需要自己按它的语法改写特别是函数定义部分差别很大。提示确认 shell 之后先把这个文件备份一份。改配置改崩了导致终端起不来是很常见的事故有备份能三秒回滚。2.2 加载时机登录 shell 和交互式非登录 shell 不是一回事这是最容易踩的坑。简单理解你打开一个新的终端窗口通常是交互式非登录 shell它只读~/.zshrc或~/.bashrc而你通过远程登录、或者在某些系统上打开特定终端拿到的是登录 shell它读的是另一套文件。如果你把快捷指令写进了只有登录 shell 才读的文件里那么你日常开窗口就不生效。解决思路有两种。第一种是只写一个主文件其他地方 source 它。我习惯把真正的逻辑全部写进~/.mytools/aliases.sh和~/.mytools/functions.sh然后在~/.zshrc末尾加# 加载自定义快捷指令 if [ -f $HOME/.mytools/aliases.sh ]; then . $HOME/.mytools/aliases.sh fi if [ -f $HOME/.mytools/functions.sh ]; then . $HOME/.mytools/functions.sh fi第二种是在登录配置文件里再 source 一次主配置。如果你在~/.bash_profile里写一行. ~/.bashrc两边就统一了。这么做的好处是不用记两套路径缺点是如果两边都写了加载逻辑可能出现重复定义所以加个判断更保险。我习惯用.点号而不是source因为点号是 POSIX 标准写法在绝大多数 shell 里都能用source在某些精简环境下不存在。这个小细节在跨机器迁移时救过我好几次。2.3 生效与回滚把验证做成习惯动作每次改动配置后标准的验证动作是先exec $SHELL -l重启一个干净 shell或者直接开一个新窗口而不是在当前窗口source一下就觉得完事了。原因是source会叠加在当前环境上有些覆盖类的问题在当前窗口看不出来只有在全新环境里才暴露。回滚的做法我也说下我把指令库目录本身做成了本地版本管理每次大改动前提交一次。这样一旦某个改动导致终端行为异常一条命令就能回到上一个可用状态。这个习惯看起来小题大做但在我把PATH顺序改错、导致所有命令都找不到的那次事故里它让我少花了至少二十分钟。3. 第一层别名能做什么不能做什么别名是投入产出比最高的一层。一条alias写下去几十毫秒的事长期收益却很明显。但它有个硬边界很多人一开始没意识到写了几十条之后才发现一半场景根本覆盖不了。3.1 别名的本质是文本替换不是函数调用理解别名的关键一句话它是命令行的开头几个字符被替换成另一串字符不是在执行时调用。所以你写的alias llls -alh实际效果是你敲ll的时候shell 把行首的ll换成ls -alh后面跟的东西原样接上。这也解释了为什么ll /tmp能正常工作——替换后变成ls -alh /tmp。定义语法要小心引号。推荐用单引号因为双引号里的变量会在定义时就被展开而单引号保留到执行时# 推荐单引号内容原样保留 alias gstgit status -sb # 不推荐双引号$ 会被提前展开 alias nowdate %Y-%m-%d第二行在多数情况下也能跑因为它没有用到需要延迟展开的变量但一旦你的命令里含$或者反引号双引号就会出问题。统一用单引号能避免这个隐患除非你确实需要在定义时展开。查看当前所有别名用alias查看某一条用alias 名字。取消用unalias 名字一次性清空用unalias -a慎用会把你所有别名干掉。3.2 带参数为什么别名撑不住很多人卡在我想给别名传参数这一步。答案是别名本身不支持参数占位你只能靠把参数追加到末尾这种天然行为。比如alias gcogit checkout敲gco main会变成git checkout main这在参数刚好在末尾时够用。但如果参数要插在中间比如你想让gco main变成git checkout main --或者参数要出现两次别名就彻底没辙了。这时候有三条路。第一条是切函数最正规。第二条是利用 shell 的!$、!!这类历史展开属于偏门技巧可读性差。第三条是改成独立脚本。我的建议是只要参数位置不是简单的末尾追加立刻换函数别在别名上硬拗拗出来的东西三个月后自己都看不懂。注意别名在非交互式场景比如脚本内部默认是不展开的除非开启shopt -s expand_aliases。所以别指望在脚本里用你定义的别名脚本里老老实实写完整命令或者直接调脚本。3.3 我日常在用的别名清单下面这些是我用了两年以上、已经形成肌肉记忆的条目。你不用全抄挑符合自己工作流的# 目录与文件 alias llls -alh alias ..cd .. alias ...cd ../.. alias dusdu -sh * | sort -rh | head -20 # 版本管理把 git 换成最常敲的动词 alias gstgit status -sb alias gcogit checkout alias gbrgit branch -vv alias glggit log --oneline --graph -20 # 检索 alias fgrep-rgrep -rn --exclude-dir.git # 安全网 alias rmecho 请用 deldir 或明确 rm -i 确认; false最后一条是刻意设计的防误删把rm拦下来逼自己想清楚。这种拦截类别名要不要加取决于你的操作习惯我加上之后确实少犯过一次错误。但也要注意某些工具的安装脚本里会调用rm被拦下来会导致脚本失败所以我一般只在个人机器上开公共环境不开。4. 第二层用函数写带脑子的快捷指令函数是真正的生产力层。它把一段逻辑固定成一个名字能接参数、能判断、能出错退出。我日常最常用的二三十个快捷指令绝大多数都是函数形态。4.1 函数定义、参数传递与默认值函数的基本骨架如下两行写法在 shell 里到处能用mkcd() { # 建目录并进入参数为空时直接报错 if [ -z $1 ]; then echo 用法: mkcd 目录名 2 return 1 fi mkdir -p $1 cd $1 || return 1 }这里有几个必须养成的习惯。第一参数一定要加双引号。mkdir -p $1在目录名带空格时会裂成两个参数加引号才是mkdir -p $1。这个坑我踩过不止一次尤其是处理文件名时。第二报错信息走标准错误用2重定向这样在被管道调用时不会污染正常输出。第三函数里用return脚本里用exit混用会导致外层 shell 意外退出。参数位说明一下$1到$9是前九个参数$是全部参数$#是参数个数。如果参数超过九个就得用shift配合或者直接改用$遍历。日常快捷指令很少超过三四个参数所以前九个够用。带默认值的写法我常用这种xlog() { local target${1:-./app.log} local lines${2:-100} tail -n $lines $target }${1:-默认值}的意思是参数一为空就用默认值这样xlog不带参数也能跑xlog ./other.log只覆盖第一个第二个仍用默认。这种能不填就不填的设计对高频指令非常重要可以省掉大量回忆参数顺序的时间。4.2 变量校验与前置检查函数里最容易忽略的是前置检查。写一个打包函数如果当前目录没有目标文件它不该默默生成一个空包然后报一堆莫名其妙的下游错误而是应该在入口就拦住。xpack() { local src${1:-.} local out${2:-archive.tar.gz} if [ ! -e $src ]; then echo 错误: 路径不存在 - $src 2 return 2 fi if [ -e $out ]; then echo 错误: 目标已存在 - $out 2 return 3 fi tar -czf $out $src || return 4 echo 已生成: $out ($(du -h $out | cut -f1)) }给不同错误分配不同退出码return 2、return 3看似多余实际很有用如果将来这个函数被别的脚本调用外层就能根据退出码判断到底是源不存在还是目标冲突做出不同处理。这就是函数相对别名的最大价值——它能表达失败。还有一点值得强调函数里尽量用local声明变量。不加local的变量会污染当前 shell 的全局命名空间你定义一个临时变量叫i结果把外层某个循环的计数器覆盖了这种 bug 极难排查。我现在的习惯是函数内部所有变量一律local一个不留。4.3 危险动作确认提示与干跑模式涉及删除、覆盖、批量移动的快捷指令必须加安全阀。我的做法是两件套交互确认加干跑开关。xclean() { local dir${1:-./build} local dry${DRY_RUN:-0} if [ ! -d $dir ]; then echo 目录不存在: $dir 2 return 1 fi echo 将要清理: $dir echo 包含: $(find $dir -type f | wc -l) 个文件, $(du -sh $dir | cut -f1) if [ $dry 1 ]; then echo [干跑] 不执行实际删除 return 0 fi printf 确认执行? (y/N) read -r ans case $ans in y|Y) rm -rf $dir ;; *) echo 已取消; return 0 ;; esac }干跑模式用环境变量控制DRY_RUN1 xclean比再写一个参数位更省事也更符合习惯。确认提示用read -r一定带-r否则反斜杠会被吃掉导致输入异常。默认值设成N大写表示默认选项这样直接回车就是取消符合危险操作默认拒绝的原则。注意rm -rf后面的变量一定要加引号并做过存在性判断。变量为空时rm -rf $dir会变成rm -rf虽然通常不致命但某些组合写法下会误伤当前目录。稳妥的写法是先判断[ -n $dir ]再执行。4.4 完整实例一键归档当前项目把前面的知识点组合起来写一个我每天都会用的指令把当前项目里需要保留的东西打包归档跳过依赖目录和临时文件。xsnap() { local name${1:-snapshot} local stamp stamp$(date %Y%m%d-%H%M%S) local out${name}-${stamp}.tar.gz local excludes() for d in node_modules .git target dist build .venv __pycache__; do [ -d ./$d ] excludes(--exclude$d) done tar -czf $out ${excludes[]} . 2/dev/null || { echo 归档失败 2 return 1 } echo 完成: $out echo 大小: $(du -h $out | cut -f1) echo 跳过目录: ${excludes[*]:-无} }这段里有几个细节值得单独说。excludes用数组并逐个判断存在性避免给tar传不存在的排除项${excludes[]}加引号展开保证路径带空格时不出问题${excludes[*]:-无}在数组为空时显示无避免输出里出现空行。这些细节单看都很小堆起来就是这个工具用着顺手和这个工具总有点别扭的差别。实测下来对一个中等规模的代码项目归档从原来手敲七八个排除参数、平均耗时二十多秒变成敲四个字符加回车、三秒内完成。省下的不只是时间主要是避免了又忘了排除某个目录、包里多了几百兆的重复劳动。5. 第三层独立脚本把复杂逻辑搬出交互式 shell当逻辑超过一屏、或者你需要在编辑器里的外部命令、定时任务、其他脚本里复用同一个动作时就该写成独立脚本了。独立脚本的最大价值是解耦它不依赖你当前的 shell 状态可以在任何地方被调用。5.1 脚本骨架三行头部定生死我所有脚本都从这几行开始从不省略#!/usr/bin/env bash set -euo pipefail IFS$\n\t第一行是解释器声明用env bash而不是写死绝对路径因为不同系统上 bash 的位置可能不一样。第二行是三道保险-e表示任何命令失败立刻退出避免错误被掩盖后继续执行-u表示引用未定义变量时报错这在拼错变量名时能立刻发现问题-o pipefail表示管道中任何一个环节失败都算失败而不是只看最后一个。第三行把字段分隔符改成换行和制表符防止文件名里带空格时被拆开。这三行看起来不起眼但能拦掉相当一部分隐蔽 bug。我印象最深的一次是脚本里有个变量名拼错没有-u的话它会静默变成空字符串然后脚本照常跑完、删掉了一堆本来不该删的东西。有-u就直接报错停在那行了。不过-e也有副作用某些允许失败的命令比如grep没匹配到返回 1会意外中止脚本。处理办法是在这类命令后面加|| true或者放进if判断里。这个坑我踩过后来养成习惯——凡是允许失败的命令显式表态。5.2 参数解析与帮助信息参数少的时候手写case就够了不需要引入复杂框架usage() { cat EOF 用法: xrename [选项] 前缀 选项: -d, --dir 路径 指定目录, 默认当前目录 -n, --dry 干跑, 只打印不执行 -h, --help 显示帮助 EOF } dir. dry0 while [ $# -gt 0 ]; do case $1 in -d|--dir) dir${2:-}; shift 2 ;; -n|--dry) dry1; shift ;; -h|--help) usage; exit 0 ;; --) shift; break ;; -*) echo 未知选项: $1 2; usage 2; exit 1 ;; *) break ;; esac done prefix${1:-} if [ -z $prefix ]; then usage 2 exit 1 fi几个要点。usage用 heredoc 加单引号包裹EOF这样里面的变量不会被展开帮助文本能原样输出。每个选项处理后都要shift对应次数-d带一个值所以shift 2其余shift 1。未知选项必须报错退出不能默默忽略否则用户打错一个字母、命令按错误意图执行后果可能很严重。--这个约定也值得实现它表示后面的都是位置参数不再当选项解析。当你的文件名本身以-开头时这个约定就是救命的。5.3 放进 PATH 与权限管理脚本写完后三件事给执行权限、放进bin/、确保bin/在PATH里。chmod 755 ~/.mytools/bin/xrename权限设755而不是777。755是所有者可读写执行、其他人可读可执行够用且安全。777让所有人都能改你的脚本是完全没必要的敞口。PATH的追加我放在 shell 配置里但要放在前面还是放在后面有讲究# 放在前面优先级高于系统命令 export PATH$HOME/.mytools/bin:$PATH放前面意味着如果你的脚本和系统命令重名你的会胜出。这既是便利也是风险所以我还是回到那条原则起不冲突的名字。放了之后用command -v 脚本名验证是否真的能被找到这一步比which更可靠因为which在某些 shell 里行为不一致。5.4 完整实例批量重命名脚本把上面的骨架组合成一个能直接用的脚本功能是把指定目录下的文件按序号加前缀重命名#!/usr/bin/env bash set -euo pipefail IFS$\n\t dir. prefix dry0 idx1 while [ $# -gt 0 ]; do case $1 in -d|--dir) dir${2:-}; shift 2 ;; -n|--dry) dry1; shift ;; -p|--prefix) prefix${2:-}; shift 2 ;; *) echo 未知参数: $1 2; exit 1 ;; esac done [ -d $dir ] || { echo 目录不存在: $dir 2; exit 2; } cd $dir for f in *; do [ -f $f ] || continue new$(printf %03d_%s%s $idx $prefix $f) if [ $dry -eq 1 ]; then printf [干跑] %s - %s\n $f $new else mv -- $f $new printf %s - %s\n $f $new fi idx$((idx 1)) done这里mv --里的双横线很重要它告诉mv后面不再有选项防止文件名以横线开头时被误判。printf %03d生成定长三位序号保证排序稳定。整个脚本先跑一遍-n干跑确认输出符合预期再实际执行这是我处理任何批量操作的标准流程从来不走捷径。6. 让它好用补全、帮助与启动性能快捷指令能用只是及格线好用需要额外三样东西补全让你不用记完整名字帮助让你三个月后还记得怎么用懒加载让你的终端启动不发胖。6.1 命令补全省掉一半的输入和记忆大多数 shell 支持自定义补全。以 zsh 为例最简单的做法是用compdef给函数挂上已有命令的补全规则# 让 xlog 复用 tail 的补全补文件路径 compdef _tail xlog 2/dev/null || true更实用的场景是给目录参数挂上目录补全。如果你的函数第一个参数是目录补全规则要写成补全目录而不是文件敲 Tab 的时候才不会列出一堆文件把你淹没。这个细节听起来小但每天用几十次的指令补全体验差一点累积起来就是持续的摩擦。对于独立脚本可以在bin/旁边放一个补全定义文件在 shell 配置里加载。我一般只给最常用的三五个脚本写补全剩下的靠 Tab 的默认文件补全也够用。写补全的投入产出比只在每天用十次以上的指令上才划算。6.2 自建帮助一条命令列出你所有的家伙什指令攒到三十个以上最大的问题变成我到底有哪些、分别干什么。我的解法是给整个指令库配一个统一的帮助入口xhelp() { local f$HOME/.mytools/aliases.sh local g$HOME/.mytools/functions.sh echo 自定义指令 [ -f $f ] grep -E ^alias $f | sed s/^alias // [ -f $g ] grep -E ^[a-zA-Z0-9_]\(\) $g | sed s/().*// echo 脚本 ls -1 $HOME/.mytools/bin 2/dev/null }思路很简单从源文件里反向生成清单。这样你只要保持每次新增指令时在前面加一行注释帮助就是自动维护的不会出现文档写的是三年前的版本这种情况。我还会在每条定义上一行写# 用途...然后用grep -B1一起抓出来效果更完整。这种文档从代码里长出来的做法比单独维护一份 README 靠谱得多。我试过维护 README两个星期就过期了。6.3 启动性能别让快捷指令拖慢你的终端这是很多人忽略的隐性成本。你在配置文件里写了五十个复杂函数每次开终端都要完整解析一遍启动时间可能从 80 毫秒涨到 400 毫秒。单次感知不明显但一天开二十次终端那份迟滞感会持续存在。我的优化办法有两个。第一把不常用的逻辑挪到独立脚本配置文件里只留高频别名和少量函数。第二测量后再优化不要凭感觉。测量启动耗时的方法# 连续测三次看平均 for i in 1 2 3; do /usr/bin/time -f %e 秒 zsh -i -c exit done如果发现某个文件拖慢了启动可以逐个注释掉再测二分法定位。我上次定位到一个函数里调用了外部命令做初始化改成就用时再算之后启动从 320 毫秒降到 110 毫秒。这个优化的思路和写前端代码一样启动路径上不放重活。7. 常见问题我踩过的坑和排查顺序下面这些是我真实遇到过的问题按出现频率从高到低排列。遇到快捷指令不生效基本可以按这个顺序排查。7.1 命令找不到、别名不生效排查顺序建议这样走确认加载了没type 指令名。输出里带is an alias或is a function说明定义成功了输出not found说明没加载。确认改的是当前 shell 的配置文件用前面ps -p $$的方法确认。确认文件路径没写错ls -l ~/.mytools/aliases.sh。确认语法没报错在当前窗口手动. ~/.mytools/aliases.sh如果报错会直接打出来。确认没有覆盖同一个名字被定义两次后面的会赢用type看最终生效版本。我遇到最多的是第五条。有次我改了一个函数改完在新窗口测试没问题但在老窗口里还是旧行为排查了十分钟才反应过来是没重开。从那以后我所有验证都在新窗口做。7.2 引号、空格与路径的那些坑这一类的典型症状是参数只有一个但脚本收到两个或者文件明明存在却说找不到。根源基本都是空格。变量引用一律加双引号$var、${arr[]}。位置参数多的时候用$而不是$*。mv、rm、cp的目标路径前加--防止文件名以横线开头被当成选项。需要按行读取文件内容时用while IFS read -r line不要用for line in $(cat file)后者遇到空格就断。这些规则记起来不难难的是每次都执行。我的办法是把它们变成肌肉记忆写的时候不假思索地加引号宁可多几个字符。7.3 脚本在不同环境下的兼容问题同一个脚本在你机器上跑得好好的换台机器就报语法错。常见原因有三个一是用了[[ ]]这种 bash 专属语法但解释器声明成了sh二是依赖了某个只有你机器上装了的外部命令三是用了#!/bin/bash的绝对路径而目标机器上 bash 在别的目录。统一解法解释器声明用#!/usr/bin/env bash只用 bash 语法外部依赖在脚本开头做存在性检查for cmd in tar gzip find; do command -v $cmd /dev/null 21 || { echo 缺少依赖: $cmd 2 exit 127 } done退出码127是命令未找到的约定值用约定值能让调用方准确判断失败类型比随便返回 1 规范。7.4 问题速查表把这节的内容压缩成一张表贴在备忘里出问题时先扫一眼症状最可能原因快速验证别名不生效配置文件不对或未重开窗口type 名字提示命令未找到PATH未包含脚本目录command -v 名字参数被拆成多个变量未加引号打印$#看个数脚本中途静默退出set -e遇上允许失败的命令加 打开终端明显变慢配置里有重活计时对比注释前后目标文件不存在文件名含空格或被当选项加引号、加--换机器语法报错解释器与语法不匹配检查首行声明最后分享一个我个人认为最有价值的习惯任何涉及批量修改、删除、覆盖的快捷指令第一遍必须用干跑模式跑一次把输出看一遍再执行。这多花的十秒钟拦住的是那种手一抖几百个文件全乱的事故。我给自己定的规矩是没有干跑模式的危险指令不许进指令库这条规矩执行两年省下的麻烦远超过它带来的不便。指令库这个东西一开始会很薄可能就十来条。它的价值不在于一次写多少而在于你会不会持续往里加——每当你发现自己第二次手敲同一串命令那就是该把它收进去的信号。半年之后回头看你会发现终端里那些真正顺手的动作全都是这么一点一点攒出来的。