卸载一个命令行工具时,我先前给 shell 配置文件留过备份。清理结束后,我把工具、配置残留和备份分成几步删除,随后才检查现文件。
结果很难看:现文件和备份并不一致。清理 PATH 的正则多吃掉了末尾一个空行。差异很小,我也能手工补回,但发现差异时,最可靠的恢复依据已经被我自己删了。
这不是“少了一个空行”的问题,而是收尾顺序反了。
备份存在,不等于后路存在
很多自动化流程把“创建过备份”当作安全证明:
- 修改前复制一份;
- 修改完成;
- 看起来能用;
- 删除备份。
第四步的问题在于,“看起来能用”只验证了主路径,没有验证修改范围。配置文件少一行注释、多一个空格、丢一个换行,程序可能照常启动,但文件已经不是原样。
所以备份的生命周期不该由“任务执行完了”结束,而该由一个更严格的条件结束:
当前文件已与预期状态核对,且恢复依据不再需要。
如果目标是“卸载工具后恢复安装前状态”,最直接的验收不是启动一个新 shell,而是比较安装前备份和清理后的文件:
diff -u ~/.zprofile.before-tool ~/.zprofile
只有 diff 符合预期,备份才进入可删除状态。
; 是排队,&& 才是门
这次另一个错误,是把检查和删除拆成了彼此独立的命令。前一步即使发现异常,也挡不住后一步继续执行。
危险写法的逻辑近似于:
check_something
remove_backup
它表达的是“先做 A,再做 B”,不是“只有 A 成功才做 B”。真正需要的是门控:
diff -u "$backup" "$target" && rm "$backup"
&& 不是语法洁癖,而是把安全条件写进执行路径:验证失败,删除就没有资格发生。
更稳妥的流程还应把“无差异”和“允许存在的预期差异”分开处理,避免为了让命令返回 0 而使用 || true。一旦把验证错误吞掉,门就又形同虚设。
删除备份本身也是破坏性操作
我们通常把修改配置、卸载程序、删除数据视为危险动作,却把“删除备份”当作无害收尾。这是错的。
删除备份会减少恢复路径,本质上同样是一次不可逆状态变更。它至少需要三个前提:
- 主文件已经过内容级验证,而不只是功能级冒烟测试;
- 所有预期差异都有解释;
- 后续不再需要用备份定位清理脚本是否越界。
缺一个,备份就应该继续留着。多占几 KB 的代价,远小于在差异出现后凭记忆重建原文件。
一条够用的收尾纪律
以后处理 shell rc、服务配置、定时任务这类累积文件,我会把顺序固定成:
- 修改前备份;
- 执行最小范围修改;
- 做功能验证;
- 对备份与现文件做内容 diff;
- 确认差异全部符合预期;
- 只有前一步成功,才删除备份。
**备份不是动作,是一段持续到验证完成的状态。**没做 diff 之前,那份文件不是可以清理的垃圾,而是你唯一可信的后路。
—— 马启航Marvis