AI 编程助手生成的迁移脚本、清理命令等往往语法正确但逻辑有破坏性;文章给出具体干运行检验流程:rm 用 find 先预览文件列表、mv/cp 用 -n 模拟检查,避免真实操作不可逆。
几乎每个 AI 辅助编程的工作流,最后都是由模型给出一条 shell 命令。数据库迁移、清理脚本、find ... -delete。而这种失败的模式通常不是恶意的——而是一条看起来合理但实际错误。它语法正确、表述自信,但对你的实际文件系统结构有着微妙的破坏性。
所以我养成了一个小习惯:AI 助手给出的任何破坏性或不可逆命令,在它接触真实目录之前,都要经过一个 dry-run 脚手架。以下是这个脚手架的实现、测试计划,以及这种方法在哪些地方会失效。
诀窍在于不再把 AI 输出当作一条命令,而是把它当作一份提案——必须通过断言才能执行。对于文件操作,大多数命令都有 dry-run 模式或等价列表模式:
rm -rf path → 先跑 find path -type f | head,检查数量和范围
mv/cp 批量操作 → 先用 -n(不覆盖)和 echo 跑一遍
git clean -fd → 永远先跑 git clean -nd
SQL 迁移 → 在事务内执行,然后回滚
我把它封装成了一个小 shell 脚本,放在 ~/bin/ai-guard.sh:
#!/usr/bin/env bash
# ai-guard.sh — inspect a proposed command before running it.
# Usage: ai-guard.sh <sandbox_dir> -- <command...>
set -euo pipefail
SANDBOX="$1"; shift
[ "$1" = "--" ] && shift
if [ ! -d "$SANDBOX" ]; then
echo "Sandbox dir '$SANDBOX' does not exist. Refusing." >&2
exit 1
fi
# Rule 1: never let the command reference anything outside the sandbox.
for arg in "$@"; do
case "$arg" in
/*|*..*)
echo "BLOCKED: argument '$arg' escapes the sandbox." >&2
exit 2 ;;
esac
done
# Rule 2: snapshot file list before, run inside sandbox, diff after.
cd "$SANDBOX"
find . -type f | sort > /tmp/before.txt
echo ">>> Running inside $SANDBOX: $*"
"$@" || true
find . -type f | sort > /tmp/after.txt
echo ">>> Files changed:"
diff /tmp/before.txt /tmp/after.txt || true
工作流是这样的:我把命令要操作的目标目录结构复制一份到临时目录(只要骨架加上几个哨兵文件即可,cp -r --parents 或一个小 fixture 脚本就能搞定),然后把 AI 提出的命令套上 guard 跑一遍,读 diff。只有 diff 符合我的意图,我才会去跑真实命令——而且即使那样,也先跑 dry-run 版本。
上个月一个助手建议用这条命令清理嵌套的构建产物:
find . -name "dist" -type d -exec rm -rf {} +
看起来没问题。但跑在我实际的仓库根目录下,它同时也匹配到了 packages/e2e/fixtures/dist——这是一个提交到仓库的 fixture 目录,不是构建产物。沙盒脚手架立刻标记出了这个问题,因为我的 fixture 副本里包含了那个目录,而 diff 显示了意料之外的删除操作。修复方法是加上 -not -path "*/fixtures/*",如果我直接把命令粘贴到终端,绝对不会想到去检查这一条。
这个模式值得内化:AI 的命令对它想象中的仓库是正确的,而不是对我实际拥有的仓库。沙盒 diff 强制把这种不匹配暴露到表面上。
一个实际的提示:这个习惯能成倍增加你与模型迭代的次数。我很少直接接受第一条建议的命令——我会要求一个更安全的变体、一个 dry-run 版本,或者要求解释边界情况,而每一次都是一次额外的往返。在付费计量上这样迭代会增加摩擦,这确实是人们跳过这一步的真正原因。我一直在用 MonkeyCode 跑这个循环,它提供免费模型访问加上免费服务器选项,所以多出来的"先给我 dry-run 版本"这类提示不花任何成本。披露:本文是 MonkeyCode 产品推广的一部分。不过上面的脚手架不依赖任何特定工具——无论命令来自助手、队友还是 2014 年的 Stack Overflow 答案,它的表现都一样。
如果你想采用这个方法,以下是我对任何 AI 建议的删除、移动、覆盖或迁移命令运行的最小检查清单:
范围检查:命令是否引用了绝对路径或 ..?如果是的,任何操作之前先重写。
Fixture 回放:在 /tmp 中重建目标结构,加入哨兵文件,包括边界情况(fixture、symlink、dotfile)。
先跑 Dry-run:使用 -n、--dry-run、git clean -nd,或 EXPLAIN/BEGIN ... ROLLBACK 等价形式。没有 dry-run 模式本身就一个红旗。
Diff 审查:对比前后的文件列表。diff 中出现任何意外路径 = 停止。
可逆性追问:如果这条命令在真实运行中出错了,我的恢复方案是什么?没有答案(没有备份、没有 VCS)= 暂时不要跑。
这个脚手架是安全网,不是验证器。它告诉你一条命令对文件做了什么,但它无法告诉你一次迁移是否产生了语义上正确的数据。
它对非本地化的损害无能为力:API 调用、CI 触发、package 发布、任何触碰共享状态的操作。这些需要环境级别的隔离(staging 项目、 scoped token),不是目录沙盒能解决的。
基于路径的拦截是朴素的。一条刻意奇怪的命令可以在不出现可疑参数的情况下造成损害。把这个 guard 当作绊线,而不是证明。
它给每条命令增加了一两分钟。这就是设计意图——但这意味着你会忍不住"就这一次"跳过它,特别是对于那些看起来很简单的命令。以我的经验,看起来简单的命令恰恰是最容易被删掉 fixture 的地方。
如果你的 AI 助手从来不为你生成 shell/SQL/基础设施命令——比如你只用它来做编辑器内的补全——那这个脚手架就是多余的。而如果你的团队已经有了完善的临时环境方案(每个任务一个 devcontainer、preview 环境、一次性 VM),用那个就好;它严格来说比我的小脚本更强。
但如果你是一个独立开发者或小团队,一直在把建议的命令直接复制粘贴到终端:在某一天 AI 助手自信地删掉一个没有 git 历史的东西之前,养成 fixture 习惯。如果你每天已经在和一个模型迭代,试试把迭代路径导向免费层——MonkeyCode 的免费模型和免费服务器是一个选项——把省下的预算花在更偏执上,而不是更随意。