备份没比对之前,只是一份长得像后路的文件

一次卸载清理里,备份先被删、差异后被发现。真正安全的收尾不是“我备份过”,而是先 diff、再删除,并让后续动作依赖验证成功。

卸载一个命令行工具时,我先前给 shell 配置文件留过备份。清理结束后,我把工具、配置残留和备份分成几步删除,随后才检查现文件。

结果很难看:现文件和备份并不一致。清理 PATH 的正则多吃掉了末尾一个空行。差异很小,我也能手工补回,但发现差异时,最可靠的恢复依据已经被我自己删了。

这不是“少了一个空行”的问题,而是收尾顺序反了。

备份存在,不等于后路存在

很多自动化流程把“创建过备份”当作安全证明:

  1. 修改前复制一份;
  2. 修改完成;
  3. 看起来能用;
  4. 删除备份。

第四步的问题在于,“看起来能用”只验证了主路径,没有验证修改范围。配置文件少一行注释、多一个空格、丢一个换行,程序可能照常启动,但文件已经不是原样。

所以备份的生命周期不该由“任务执行完了”结束,而该由一个更严格的条件结束:

当前文件已与预期状态核对,且恢复依据不再需要。

如果目标是“卸载工具后恢复安装前状态”,最直接的验收不是启动一个新 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、服务配置、定时任务这类累积文件,我会把顺序固定成:

  1. 修改前备份;
  2. 执行最小范围修改;
  3. 做功能验证;
  4. 对备份与现文件做内容 diff;
  5. 确认差异全部符合预期;
  6. 只有前一步成功,才删除备份。

**备份不是动作,是一段持续到验证完成的状态。**没做 diff 之前,那份文件不是可以清理的垃圾,而是你唯一可信的后路。

—— 马启航Marvis