上周帮人收拾一台常年开着的下载机,源目录是本地一块 4T 盘,备份目标是 NAS 上挂过来的网络盘。他一直在用一个批处理跑 robocopy D:\Media \\nas\backup\Media /MIR,跑了小半年,从没出过事。这次他顺手把几个装机用的 iso 和一份路由器配置包丢在备份目录里,想省点本地空间。第二天再看,那几个文件没了。
不是 NAS 的问题,也不是硬盘坏。/MIR 就是这个意思。
先说清楚 /MIR 等于什么
/MIR 是 /E 加 /PURGE 的缩写。/E 负责把源目录(含空子目录)铺到目标,/PURGE 负责把目标端"源里没有"的东西删掉。两条合起来才是镜像:跑完之后目标端和源端长得一模一样,包括源端不存在这个事实。
所以在"备份"这个场景里,它的语义其实是双向的——数据从源流向目标,删除也一样流过去。你手动往备份盘里加的任何东西,下一轮就会被当成垃圾清掉。
我搭了一组目录复现了一遍,源端五个文件,目标端多放一个 删除我.txt 和一个 手动备份\旧存档.7z:
- 多余目录:手动备份
- 多余文件:旧存档.7z,512 字节
- 多余文件:删除我.txt,36 字节
- 文件合计:复制 5,跳过 0,其他 548 字节
- 退出码:3
跑完再看目标目录,那两个东西确实没了,手动备份 这个文件夹本身也没了。退出码 3 的意思是"有文件被复制(1)+ 有多余项被清掉(2)",两个位同时置上了。这个码很多人只看是不是 0,其实 0 到 7 都算成功,只有大于等于 8 才是真出错。
删之前,/L 会先念一遍名单
同样一条命令,末尾加 /L,它一个字都不动,只把打算干的事打出来:
- *多余目录 -1 dst\手动备份\
- *多余文件 512 旧存档.7z
- *多余文件 36 删除我.txt
- 较旧的 15 同名.txt
- 新文件 10 说明.txt
带星号的那几行就是要被删的。这一步几乎不花时间,扫一遍目录结构而已,但它是唯一能让你在删除发生前看见名单的机会。定时任务里我一般让它先跑一遍 /L 写日志,人工看一眼再放行正式的那条。真出事的时候,日志里那几行就是证据。
目标端改过的文件,默认会被源端盖掉
上面输出里 同名.txt 标的是"较旧的"。我在源端写的是 源端版本 v1(10 字节),目标端那份是手工改过的 备份盘上改过的版本 v9(15 字节,时间戳更晚)。默认参数下没有 /XO,robocopy 的判据是"目标端比源端旧",于是源端把这个文件盖了过去。跑完 cat 一下,内容确实变回了 源端版本 v1。
这条比删文件更阴,因为它不删,它覆盖。删掉的东西你至少能发现少了,被覆盖的是同一个文件名、同一个位置,你不打开就不知道里面已经换人了。
想保住目标端更新的版本,加 /XO。同一组文件加 /XO 再跑一次预演,数字变成:文件合计 2,复制 0,跳过 2。两个都不动了。代价是源端真正更新的内容也进不去——/XO 是个单向的选择,不是"两边都留",它只是让较新的那个赢。
判重只看大小和时间戳,不看内容
这是最容易埋雷的一条。我做了个文件,源端 4 字节 AAAA,目标端 4 字节 BBBB,然后把两边的修改时间都改成同一个值。再跑 /MIR /L,这个文件压根没出现在输出里——复制 0,跳过 1。
也就是说:只要大小一样、时间戳落在容差内,robocopy 就认为两边是同一个文件,一个字节都不读。文件内容被改坏了、被截断后又补回来了、被同名替换过,它统统不知道。
robocopy 没有内容比对选项,/IS 只能强制重写相同文件,那是重拷一遍,不是校验。真在意完整性,得在备份完之后单独算一遍哈希。这一条决定了镜像适合"对齐",不适合"验证"。
有些目录不该跟着一起镜像
/XD 是排除目录,被它点名的路径在两边都不参与比对。我测了一组:源端和目标端各有一个 缓存 目录,目标端缓存里放了个 y.dat,另外目标端根下再放一个 多余.txt,命令是 /MIR /XD 缓存。
结果是 y.dat 活着,多余.txt 照删。因为 缓存 整个从扫描里被摘出去了,robocopy 根本没看见它,也就谈不上"目标端有而源端没有"。
这个用法在 NAS 备份里很实用:目标端放回收站、缩略图缓存、.@__thumb 这种由 NAS 自己维护的目录,用 /XD 点掉,就能让它们免于被镜像清空。Synology 那套 @eaDir、群晖照片的转码缓存,都属于这一类——它们本来就不该出现在源端,硬镜像过去只会被当成垃圾。
目录联接会被跟进去,同一个文件备两份
源端如果有个 mklink /J 做的目录联接(junction),/MIR 默认会把它当普通目录跟进去。我建了一个 链接 指向同级的 剧集,里面只有一个 a.mkv:
- 目录合计:3,复制 2
- 文件合计:2,复制 2
- 字节:200
同一个 100 字节的文件,被当成两份复制了。平时这只是浪费空间,但如果联接指向上层目录,就会出现一边复制一边发现新目录的循环,最后要么撑爆目标盘,要么跑到你手动停掉。
加 /XJ 之后:文件合计 1,复制 1,目录 2 个被跳过。联接本身不跟了。
这一条在整理影视库的人身上很常见——为了把散在几块盘上的剧集拼成一个目录树,用了一堆 junction 或符号链接,然后拿 robocopy 去备份,备份体积莫名其妙翻倍。
卡在某个文件不动,先看 /R
robocopy 默认 /R:1000000 /W:30,意思是单个文件失败重试一百万次,每次间隔 30 秒。我第一次看到这个默认值时以为是打错了。真遇到一个被播放器占着、或者正被 NAS 索引服务扫描的文件,它就会安安静静地在那里重试,界面上什么都不动,看起来像卡死。
实际用的时候建议一律带上 /R:2 /W:2,失败两次就跳过,把名字记进日志。备份任务本来就该"跳过并报告",而不是"死磕到天亮"。
另一类 16 是"没有复制任何文件"的严重错误,基本都是参数写错。我在 Git Bash 里跑时踩过一次:/MIR 被 shell 的路径转换改写成了 D:/Program Files/.../MIR,robocopy 报"无效参数 #3"。
那到底该怎么用
我的做法是把它拆成两条:日常增量用 /E /XO,只往里加、不往回删,跑多久都不会伤到目标端;需要严格对齐的时候才上 /MIR,但前面一定先跟一条 /L,日志留档。
备份盘上想放点自己的东西,就单独开一个 /XD 排掉的目录,别跟镜像目标混在一起。
最后留个问题:你们拿 NAS 当备份目标的时候,是接受"镜像"这种一边删一边同步的关系,还是宁愿目标端只增不减、空间满了再手动清?