如果你经常在 Windows 和 WSL 之间来回复制文件,大概率撞见过一个让人摸不着头脑的场景:从资源管理器里拖一个 build.sh 进 WSL 的工作目录,敲下 ls,旁边多出一个 build.sh:Zone.Identifier。删掉它,再复制一次,它又冒出来。更夸张的是,从网页下载的 zip 包解压后整个目录复制过去,里面能躺着几十个这种带冒号的文件。我第一次遇到时也懵了一下,第一反应是文件系统坏了或者中招了。排查下来才发现,这既不是病毒也不是磁盘问题,而是 Windows 的 Mark of the Web(MOTW)机制,在 WSL 跨文件系统复制时留下的一堆“翻译残渣”。这篇文章,我会把它的来龙去脉讲清楚,然后给出一套可以直接复制使用的清理脚本,包括 WSL 侧的 bash 版和 Windows 侧的 PowerShell 版,再分享几个实操中容易踩的坑。无论你是刚接触 WSL 的新手,还是被这些文件折磨过几次的老手,照着做都能一次性解决。
1. 等等,这个 Zone.Identifier 到底是个啥
1.1 先理解 Windows 的“安全标记”
要说清楚这个问题,得先认识 Windows 文件系统里的一个隐藏机制。
当文件来自互联网、局域网共享或者 U 盘等外部来源时,Windows 会在文件上打一个“来源标记”,学名叫做 Mark of the Web,简称 MOTW。浏览器下载的 exe、压缩包、文档,几乎都会带上这个标记。它的作用是让 Windows 以及 Office、浏览器等应用知道“这份文件不是本机生成的,需要多留个心眼”。比如你双击从网上下载的 .exe,系统会弹出一个蓝色的“受保护视图”或 SmartScreen 警告,靠的就是它。
那这个标记存在哪里?它并不存在文件内容里,而是存在 NTFS 文件系统的 Alternate Data Stream(ADS,替代数据流)中。这个 ADS 的名字就叫 Zone.Identifier。你可以把 NTFS 上的一个文件想象成一个文件夹,里面除了主文件内容,还能挂一堆“隐藏便签”,ADS 就是这种便签。Zone.Identifier 是其中最常用的一张,内容大概是这样的:
code复制[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://example.com/
HostUrl=https://example.com/file.zip
里面的 ZoneId 是个数字,表示文件来源区域:0 是本机,1 是局域网,2 是可信站点,3 是 Internet,4 是受限站点。3 是最常见的,因为绝大多数文件都是直接从互联网下载的。
在 Windows 里查看文件属性时,如果看到有“解除锁定”按钮,点一下,系统就会把 Zone.Identifier 这个 ADS 删掉,文件的 MOTW 标记也就没了。在 PowerShell 里,这条命令等价于:
powershell复制Unblock-File -LiteralPath .\file.zip
1.2 WSL 里为什么就变成独立文件了
问题出在 WSL 的文件系统机制上。
WSL2 跑在一个轻量虚拟机里,它的根文件系统是 ext4,ext4 有一个特点:不支持 NTFS 的 ADS。当你通过 cp 或者拖拽,把 /mnt/c/xxx/file.zip 复制到 WSL 的 ~/downloads/file.zip 时,WSL 的 9P 协议(WSL1 是 DrvFS)需要把 Windows 侧文件的所有属性都“翻译”成 Linux 侧的表示。
主文件内容很好翻译,但 ADS 怎么办?ext4 里没有对应的“隐藏便签”概念。于是 WSL 做了一个很直白的选择:把便签本身变成一个独立文件。文件名按照 原始文件名:流名称 的规则生成。
所以你会看到 build.sh 旁边多出一个 build.sh:Zone.Identifier。这里的冒号不是目录层级符号,也不是命名空间符号,就是文件名里的普通字符,冒号在 Linux 文件系统里是合法字符。这个“便签文件”的内容可能为空,也可能包含前面提到的 [ZoneTransfer] 文本,具体看 WSL 版本。
这里容易搞混的一点是:如果你在 /mnt/c 下面用 ls,是看不到这些 xxx:Zone.Identifier 文件的,因为它们仍然以 ADS 的形式藏在 NT
