双击一个文件,弹出"指定的路径或文件名太长,或者两者都太长";换命令行删它,提示"找不到路径……因为该路径不存在",可父目录里明明列得出它的名字;实在没法子,用压缩软件解压,进度条走完、退出码 0,文件却像凭空蒸发。这几件事其实是同一件事:Windows 的 Win32 文件接口对"整条路径字符串"有一道 260 个字符的坎。这篇把这道坎拆成三层——它到底量的是哪一段、为什么会同时出现"存在"和"找不到"两种结论、以及三条能当场用上的解法(映射盘符、目录联接、robocopy 搬运)各自实测到哪一步。规则原文取自微软官方文档,报错文字、退出码和命令结果来自一台 Windows 10 专业版 22H2(内部版本 19045.6466)实机的复现记录。
信息核验时点:2026-11-01 至 11-02(Windows 10 专业版 22H2,内部版本 19045.6466;Windows PowerShell 5.1.19041.6456;.NET Framework 4.8(Release 533325);系统自带 tar 为 bsdtar 3.5.2(libarchive 3.5.2);命令行里的
7z由 NanaZip 7.0(version 2610.2,x64)提供)。本文引用的四份微软文档页在核验时点均可访问,页面标注的更新时间为:Maximum Path Length Limitation 英文页 2024-07-17、同页中文版 2024-11-21,Naming Files, Paths, and Namespaces 2024-08-28,subst 2023-02-04,Robocopy 2025-03-17。文中所有"实测"都在这台机器上完成;注册表那一项因为当前会话不是管理员权限,只做了读取与写入被拒的验证,开启后的效果本文不写结论。
本文要点
- 一句话定义:260 字符限制(微软叫 MAX_PATH)量的不是文件名,而是"从盘符开始、把每一层目录名和分隔的反斜杠全部加起来"的整条路径字符串。官方原文:"在 Windows API 中(以下段落中讨论了一些例外情况),路径的最大长度为 MAX_PATH,此项被定义为 260 个字符。"三条线要分清:整条路径、目录层级、单个名字。
- 核心结论:同一台机器上,"文件在不在"会有两个互相矛盾的答案——超过限制的那一段被普通接口直接截断,于是出现"父目录列得出它、双击却说找不到""删除命令返回成功、目录里还剩一堆"。解法不是去改文件名,而是换一条更短的入口:映射盘符(subst)、目录联接(mklink /J)、或者用 robocopy 把内容搬到短根。
- 适用场景:解压多层目录的压缩包、清理项目里删不掉的深层文件夹、备份软件报"路径不存在"、别人发来的工程在自己电脑上打不开某个文件。
- 适用人群:常接手别人打包的目录树的办公人员,写脚本时被"找不到路径"反复拦住的自动化用户,以及替同事看问题的电脑"热心人"。
- 短板/边界:映射盘符和联接只在这台机器上有效,文件位置没变,发给同事照样撞墙;改注册表加应用清单那套"取消限制"是两件事,官方明确写"只影响为利用新功能而修改的应用程序",不是给老问题解决 260 的按钮。
报错里的"260 个字符",量的到底是哪一段?
结论先给:它量的不是文件名长度,而是整条路径字符串——从 D: 这个盘符开始,每一层目录名、中间每个反斜杠,全部算进去。
微软官方中文页对这件事的定义写得很清楚:"在 Windows API 中(以下段落中讨论了一些例外情况),路径的最大长度为 MAX_PATH,此项被定义为 260 个字符。"并且交代了这条字符串是怎么拼出来的:"系统按以下顺序构建本地路径:驱动器号、冒号、反斜杠、用反斜杠分隔的名称组件和终止 null 字符。"(微软文档:最大路径长度限制)
注意最后那个"终止 null 字符"——它是 C 语言字符串结尾那个不可见的占位符,也要占一格。所以能用的实际长度是 259 个字符,官方那句报错的措辞也正是"必须少于 260 个字符"。
这里最容易搞错的一点是:名字很短的文件照样能撞墙。实机上随手造的目录树,每一层目录名只有 38 个字符,五层叠起来就已经 240 个字符,再放一个 44 字符的文件名(40 个字母加 .txt),整条路径 292 个字符,直接过线。
真正要记的是三条不同的线,它们各自独立生效。
第一条:整条路径 259 个字符。 这是 Win32 文件接口的老上限,超出后普通 API 连"这个文件在哪"都解析不出来。
第二条:目录层级 248 个字符。 官方原文:"使用 API 创建目录时,指定的路径不能太长以致于无法附加 8.3 文件名(即目录名不能超过 MAX_PATH 减 12)。"(同上页)8.3 文件名是 MS-DOS 时代留下的短名格式(PROGRA~1 这种),系统要给每个目录留一个,所以创建目录时先扣掉 12 格。实机结果和这句话严丝合缝:PowerShell 一层层往下建目录,最深建到 247 个字符成功,248 个字符第一次失败,抛出的正是那句:
指定的路径或文件名太长,或者两者都太长。完全限定文件名必须少于 260 个字符,并且目录名必须少于 248 个字符。同一层用 cmd 的 mkdir 试,长度 248 时报的是另一句更短的话:文件名或扩展名太长。——同一个原因,两套措辞,这也是很多人对不上号的原因。
第三条:单个名字 255 个字符。 官方说法是"此类型的路径由反斜杠分隔的组件组成,每个组件都取决于 GetVolumeInformation 函数的 lpMaximumComponentLength 参数中返回的值(此值通常为 255 个字符)"(同上页)。这一条和总长无关,是"一个名字本身"的天花板。为了把它和 260 那道墙分开测,我用扩展长度前缀(下一节会讲)绕开总长限制,只留单名这一个变量:目录名 250 个字符能建、255 个字符能建、256 个字符失败,报的是第三种措辞:
文件名、目录名或卷标语法不正确。文件名同理:250 个字符的名字能建;255 个字符的名字再加 .txt(合计 259)就撞上这第三条线,同样报这句。
三条线之外还有半条:相对路径永远受 260 约束。官方原文:"由于 \\?\ 前缀不能与相对路径一起使用,因此相对路径总字符数始终不能超过 MAX_PATH 个字符。"(同上页)也就是说,你在脚本里写 .\子目录\文件.txt 这种相对写法,那道扩展长度的门是进不去的。
为什么文件明明在,双击却说"找不到"?
因为"看得见"和"打得开"走的是两套不同的解析路径,而它们在墙边断在不同的位置。
实机上造一个 292 个字符的文件(内容写死一串标记文字),四种接口同时问一遍,结果是互相打脸的:
| 问法 | 结果 |
|---|---|
普通路径 Test-Path | False |
普通路径 Get-Content | 报"找不到路径……因为该路径不存在。" |
在 247 字符的父目录里 Get-ChildItem -File | 列得出这个文件名,全长 292 |
| 带扩展长度前缀读取 | 正常读出内容 |
也就是说:文件确实躺在磁盘上,父目录的索引里也有它,只是普通接口在把整条字符串交给系统的那一步就把它拒了,于是给出"不存在"这个不准确的结论。官方对这类分裂有一句总纲:"Shell 和文件系统有不同的要求。可以使用 Shell 用户界面无法正确解释的 Windows API 来创建路径。"(同上)前半句解释了为什么资源管理器和文件系统口径不一致,后半句解释了为什么这种文件能被创建出来——它往往是别的程序、别的机器、别的接口留下的。
递归枚举的差别更直观。一棵最深 331 个字符的目录树,两种数法的计数结果:
$base = 'D:\tmp\l50\t11'
@(Get-ChildItem -LiteralPath $base -Recurse -Force -ErrorAction SilentlyContinue).Count
@(Get-ChildItem -LiteralPath ('\\?\' + $base) -Recurse -Force -ErrorAction SilentlyContinue).Count实机输出:普通写法 7 项,带前缀 8 项。差的那一项就是最深那个 331 字符的文件。另一棵更深的树(最深 355 个字符、其中 3 项超过 260)实测是 7 对 9。
结论:普通递归枚举是"在墙外就断了",不是"里面没东西"。 所以拿"我扫了一遍没有文件"当删除成功的证据,并不可靠。
比数错更麻烦的是删完的假成功。用 cmd 对一棵超限目录执行递归删除:
rd /s /q "D:\tmp\l50\dst"退出码 0,一个字都没报错。再数一遍:里面还剩 9 项,最深 355 个字符。这就是"我明明删掉了,怎么打开还在"的成因——rd 对进不去的那几层直接跳过,不把它当成失败。
顺带把三种报错措辞对上号,看到哪句就知道断在哪:
指定的路径或文件名太长,或者两者都太长……:调用方(多为 .NET 那套接口)主动判长度,直接拒绝,信息最诚实。未能找到路径"……"的一部分。:路径解析到某一层断了,通常是祖先目录已经超限。找不到路径"……",因为该路径不存在。:PowerShell cmdlet 的措辞,文件很可能存在,只是这条路径它读不到。
三句都指向同一个原因,但后两句会让人误判成"文件丢了""路径写错了",然后开始反复检查文件名——那是白费劲。如果提示根本不是这几句,而是"文件正在被另一个程序使用",那是另一条排查路线,见文件正在被另一个程序使用、删不掉怎么办;同样一句"打不开",也可能是文件身上的来源标记在拦,见下载的文件打不开、提示来自其他计算机怎么办。
哪四类操作最容易撞上这堵墙?
第一类:解压。 这是撞墙概率最高、也最容易误判成"压缩软件坏了"的场景。实机上先做一个包内相对层级 194 个字符的 zip(1154 字节),再挑一个本身已经 131 个字符的目标目录,两者相加 351 个字符,然后三种工具各解一次:
- PowerShell 自带的
Expand-Archive:直接失败,报的就是"指定的路径或文件名太长,或者两者都太长……"那句。 - 系统自带的
C:\Windows\System32\tar.exe -xf(bsdtar 3.5.2):退出码 0,无任何警告。 - 命令行的
7z x(这台机器上由 NanaZip 7.0 提供,C:\Users\HKC\AppData\Local\Microsoft\WindowsApps\7z.exe):退出码 0,输出里还写着"Everything is Ok"。
后两个"成功"了,但解出来的那个文件用普通路径看不见、用前缀读得到。"解压成功"和"看不见文件"同时发生,是这类问题最坑的地方。相关软件的基础用法在站内有还在用带广告的解压软件?NanaZip和7-Zip:高压缩比、完全免费的开源解压缩软件两篇,本文只谈长路径这一层。
第二类:打包。 想把超限的文件压起来,Compress-Archive 两种失败法都实测到了:直接指定那个文件,报"路径'……'不存在,或者不是有效的文件系统路径。";改成压缩它的父目录,报"未能找到路径'……'的一部分。"。
第三类:复制与搬运。 cmd 的 copy 用普通路径取那个 292 字符的文件,报"系统找不到指定的路径。",一个字节都没过去;同一对源和目的换成 robocopy,退出码 1(官方定义:全部复制成功)。
第四类:命令行 tar 叫错了程序。 这条严格说不是 260 的问题,但它长得太像,值得单独拎出来。在这台装了 Git 的机器上直接敲 tar,命中的是 Git 自带的 GNU tar 1.35,创建和解压都退出码 128,报错是:
/usr/bin/tar: Cannot connect to D: resolve failed这句"无法连接 D:"看着像权限或盘符问题,其实是 GNU 那套路径解析不认识 D:\ 这种 Windows 写法。写全绝对路径调用系统自带的那个(C:\Windows\System32\tar.exe,实测版本 bsdtar 3.5.2、libarchive 3.5.2),同样的命令退出码 0。所以遇到这句先怀疑叫错了程序,别怀疑长路径。
解法一:用 subst 给深目录发一个盘符,代价最小?
思路是:文件一个都不动,只把那条又长又深的路径换成一个短入口。
subst 是 Windows 自带的命令,官方定义:"Associates a path with a drive letter. If used without parameters, subst displays the names of the virtual drives in effect."(把某个路径关联到一个盘符上;不带参数运行则列出当前生效的虚拟盘符)语法两行:subst [<drive1>: [<drive2>:]<path>] 建立,subst <drive1>: /d 撤销(微软文档:subst)。
实机操作,目标就是那个 247 字符的目录:
subst S: "D:\tmp\l50\t12\xxxx……\yyyy……"
dir /b S:\
type "S:\ffffffffffffffffffffffffffffffffffffffff.txt"
subst S: /d结果全部通过:建立退出码 0;dir /b S:\ 直接列出那个 44 字符的文件名;type 退出码 0 并读出内容;换成 PowerShell 的 Get-Content 和 .NET 的 [System.IO.File]::ReadAllText 也照样读得到。原本 292 个字符的整条路径,这时只有 47 个字符(S:\ 加文件名)。
删除也能从这个短入口走。del "S:\ffffffff…….txt" 退出码 0,再用扩展长度前缀复核,文件确实没了——这是"删不掉的深层文件"最省事的一条路。撤销映射:subst S: /d 退出码 0,撤销后目标目录本身完好,Test-Path 仍为 True。
关于这个映射能活多久,实测只覆盖到"跨进程"这一步:映射建好之后,另起一个 cmd.exe 进程不带参数运行 subst,能列出这条映射(那次试的是 V 盘,输出 V:\: => D:\tmp\l50\s9);新开一个 PowerShell 进程读写这个盘符也正常。同一登录会话里的新窗口能用,是验证过的;重启之后还在不在,本文没有做重启验证,别当结论用。
两条边界要记住。一是官方列出的六个命令不能作用在这种虚拟盘上:chkdsk、diskcomp、diskcopy、format、label、recover(同上页);二是盘符要在 lastdrive 允许的范围内,否则 subst 会回一句 Invalid parameter - drive1:。
另一条不是官方条款、但更常踩:这个入口只在你这台机器上存在。你把 S:\ 下的文件拷给同事,他那边没有 S 盘;如果某个程序把这个短路径写进了配置或"最近打开"列表,换一台机器就指空了。
解法二:目录联接 mklink /J,能让所有程序都走短路径?
和 subst 同样是"不改文件位置,只加一个入口",区别在于入口是一个普通文件夹,而不是一个新盘符。所以任何只允许你选文件夹的程序(备份设置、IDE 的工作区、安装包解压目标)都能用它。
命令与实机结果:
mklink /J "D:\tmp\l50\j7" "D:\tmp\l50\t7\xxxx……\yyyy……"退出码 0,系统回一句 为 D:\tmp\l50\j7 <<===>> D:\tmp\l50\t7\xxxx…… 创建的联接。这个联接对象的属性是 Directory, ReparsePoint(目录、重分析点),从它进去访问那个 292 字符的文件,整条普通路径只有 58 个字符,cmd 的 type、PowerShell 的 Get-Content、.NET 的 ReadAllText、Test-Path 全部正常,Remove-Item 也能删掉里面的文件。
删联接会不会连带删掉真实内容? 这是所有人第一次用它时最担心的事,三种删法我都用测试目录验了一遍:
| 删法 | 退出码 | 联接本身 | 目标目录项数(删前 → 删后) |
|---|---|---|---|
rd "D:\tmp\l50\jn2"(不带 /s) | 0 | 已删除 | 1 → 1,没动 |
rd /s /q "D:\tmp\l50\jn1" | 0 | 已删除 | 3 → 3,没动 |
Remove-Item -Recurse -Force | 无报错 | 已删除 | 1 → 1,文件仍在 |
在这台 Windows 10 19045 上,三种写法都只删掉那个入口,被指向的目录一个字节没动。不过这是这一台机器的版本行为,动重要目录前照例先在一个测试目录上验一次。
还有一条实测的对比:同一个位置改用 mklink /D(符号链接)来建,在这台机器的当前会话里直接失败——"你没有足够的权限执行此操作。",退出码 1。这个会话不是管理员权限(whoami 是普通账户,管理员判定为 False)。目录联接(/J)在同样的会话里一次就建成,这也是本地绕长路径时优先选 /J 的原因。
解法三:robocopy 把内容搬到短根,是不是最彻底?
前两条解法都没改变文件位置,问题还在那儿,只是你现在有个短门牌。如果这棵目录树以后要长期用、要交给别人、要进版本库,那正确做法是把它搬到一个短根下面去。这件事 robocopy 能直接对超限目录做。
robocopy 对长路径的支持是写在官方参数表里的:/256 Turns off support for paths longer than 256 characters.(微软文档:Robocopy)——注意这是一个关闭支持的开关,反过来说明默认状态下它是按支持超过 256 个字符来设计的。
实机三步,源都是那个 247 字符的目录:
robocopy "D:\tmp\l50\t12\xxxx……\yyyy……" "D:\tmp\l50\u12" /E /NFL /NDL /NJH /NJS
robocopy "D:\tmp\l50\t12\xxxx……\yyyy……" "D:\tmp\l50\m12" /E /MOVE /NFL /NDL /NJH /NJS第一条(只复制):退出码 1,官方定义"1 = All files were copied successfully."。文件落在 D:\tmp\l50\u12\ffffffff…….txt,整条 59 个字符,普通路径直接读得到内容;源目录一点没动——robocopy 不带 /MOVE 就是复制,不是搬运。
第二条(搬运):退出码 1,文件在 D:\tmp\l50\m12 下同样 59 个字符、普通读取正常;那个 247 字符的目录本身已经被删掉了。往上数,原来的根下面还剩 5 个空目录,最深 209 个字符——这时它们已经回到限制以内,普通 rd /s /q 就能清掉(实测退出码 0)。/E 的含义是连空子目录一起复制,/MOVE 是"移完删源"。
顺手把降噪参数也标一下:/NFL /NDL 不列文件与目录名,/NJH /NJS 去掉任务头和任务汇总,四个都用过,不影响退出码。
/MOVE 和 /MIR 是会删东西的开关,先试跑再执行。 官方的试跑参数是 /L,说明是"列出来,但不复制、不删除、不改时间戳"("listed only (and not copied, deleted, or time stamped)")。退出码也要会读:0 是没复制也没失败,1 是全部复制成功,2 是目标里有多余项,而官方那句红线是"Any value equal to or greater than 8 indicates that there was at least one failure during the copy operation."——大于等于 8 才算真出过事。
已经删不掉的深层目录,怎么清干净?
按代价从小到大三条,都实测过。
第一条:从短入口进去删。 如果只是想清掉里面几个文件,subst 或联接过去,用普通删除即可(见上一节的 del "S:\…")。
第二条:robocopy 镜像清空,再摘掉空壳。 拿一个真正的空目录去镜像那个删不掉的树,/MIR 会把目标里多出来的项全删掉:
mkdir "D:\tmp\l50\empty12"
robocopy "D:\tmp\l50\empty12" "D:\tmp\l50\t12" /MIR /NFL /NDL /NJH /NJS
rmdir "D:\tmp\l50\t12"实测 robocopy 退出码 2(官方:"There are some additional files in the destination directory that aren't present in the source directory. No files were copied."),不加降噪参数时输出里会逐项标出 *多余目录、*多余文件,等于先给你看一遍清单;跑完超限的层级全没了,但根目录本身还在,补一条 rmdir 退出码 0 就干净了。这条的好处是过程可见,坏处是必须确认那个"空目录"真的空。
第三条:扩展长度前缀 + .NET 接口,一次到底。 单个文件:
[System.IO.File]::Delete('\\?\D:\tmp\l50\t12\xxxx……\ffffffffffffffffffffffffffffffffffffffff.txt')整棵树(第二个参数 $true 表示递归):
[System.IO.Directory]::Delete('\\?\D:\tmp\l50\t12', $true)两条实测都返回成功,删完 Test-Path 为 False。这是最省事的一条,但它的"省事"是双向的:递归删除加一个手打错的路径,就没有回收站可救。用之前先把那串路径贴到记事本里核对一遍结尾。
明确不要用的两条:rd /s /q(超限部分会被静默跳过,实测退出码 0 而树里还剩 9 项)和 Remove-Item -Recurse(实测报"未能找到路径'……'的一部分。",删不动)。
\\?\ 前缀能到 32,767 个字符,为什么不当日常用法?
它是这一切的"底层后门"。官方中文页:Windows API 的许多函数有 Unicode 版本,"以允许最大总路径长度为 32,767 个字符的扩展长度路径……要指定扩展长度路径,请使用 \\?\ 前缀。例如,\\?\D:\非常长的路径。"(微软文档:最大路径长度限制)网络共享有对应写法:\\?\UNC\server\share,其中 server 是计算机名、share 是共享名。
官方还特意给这个数字打了折:"32,767 个字符的最大路径是近似值,因为系统可能会在运行时将 \\?\ 前缀扩展为更长的字符串,并且此扩展适用于总长度。"
实机验证过能吃这个前缀的接口:.NET 的 CreateDirectory、WriteAllText、File.Exists、Delete、Directory.Delete,PowerShell 的 Get-ChildItem、Test-Path、Set-Location,以及 cmd 的 mkdir 和 dir。前面每一节里的"复核文件是否真的存在",靠的都是它。
但它有四条实打实的代价,决定了它是脚本里的工具,不是日常习惯。
第一,它要求绝对路径,而且不做字符串解析。官方原文:"这些前缀并不用作路径本身的一部分。它们指示将路径传递给系统时修改量应最少,这意味着您不能用正斜杠来表示路径分隔符,不能用句点来表示当前目录,也不能用双点来表示父目录。"(同上)也就是说 \\?\D:\a\..\b 这种写法不会退到上级,会被原样当成一个名为 .. 的目录组件。
第二,不是所有接口都认。另一篇官方文档写得很直白:"Many but not all file I/O APIs support \\?\"",同一句还补了一句要你逐个 API 查参考文档(微软文档:Naming Files, Paths, and Namespaces)。所以"加上前缀就万能"是不存在的。
第三,程序自己拼路径时会把它丢掉。很多应用内部会做规范化、取相对路径、拆分重拼,前缀在某一环被抹平,症状就是"我明明写了长路径,还是报 260"。
第四,它只解决接口层的限制,不解决软件自己的限制。一个只允许 260 的输入框、一个内部用固定长度缓冲的导入功能,前缀救不了。
顺带一条容易混的:\\.\ 是设备命名空间的写法,和 \\?\ 不是一回事,别拿它去试文件路径。
能不能直接把 260 限制关掉?注册表和清单为什么是两件事?
官方给的答案是:从 Windows 10 版本 1607 开始,"许多常见的 Win32 文件和目录函数中删除了 MAX_PATH 限制。但是,你的应用必须选择支持新行为。"而"选择支持"要同时满足两个条件——"必须设置注册表值,应用程序清单必须包含 longPathAware 元素。"(微软文档:最大路径长度限制)
注册表那一条,官方给的是 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem 下的 LongPathsEnabled(REG_DWORD)设为 1,并附了两种写法:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem]
"LongPathsEnabled"=dword:00000001New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
-Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force组策略里也有同一开关(计算机配置 → 管理模板 → 系统 → 文件系统 → 启用 Win32 长路径)。
第二段是给软件开发者的应用清单片段,缺了它,注册表改了也没用:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
<ws2:longPathAware>true</ws2:longPathAware>
</windowsSettings>
</application>官方对这件事的语气是警告式的,值得原样抄回来:"了解启用此注册表设置将只影响为利用新功能而修改的应用程序。开发人员必须将其应用声明为可感知长路径……这不是将影响所有应用程序的更改。"
还有一条会让人误判"改了没用"的机制:这个注册表值"在首次调用受影响的 Win32 文件或目录函数后,系统(每个进程)将缓存此注册表值……在进程的生存期内,将不会重新加载注册表值。为了使系统上的所有应用都能识别该值,可能需要重新启动,因为某些进程可能在设置密钥之前已经启动。"
哪些函数在放开之列?官方按类别列了一份名单,目录侧包括 CreateDirectoryW、RemoveDirectoryW、GetCurrentDirectoryW、SetCurrentDirectoryW,文件侧包括 CreateFileW、CopyFile2、DeleteFileW、FindFirstFileW、GetFileAttributesW、GetFullPathNameW、MoveFileWithProgressW、GetFinalPathNameByHandleW 等(以上均见同一页)。
这台机器上的实测情况是这样:LongPathsEnabled 这个值存在,当前为 0;读取不需要管理员权限,下面这行普通窗口就能跑:
(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name LongPathsEnabled).LongPathsEnabled但写入被拒:从当前这个非管理员会话(whoami 为 HKC,提升状态 False)执行设置,报"不允许所请求的注册表访问权。",值仍为 0。因此开启后的实际效果本文没有实测,只把官方条件列全,不替它打包票。
顺手做一个可复现的粗测,看看系统自带程序有没有声明过自己是"长路径感知"的。方法很笨但透明:把 exe 整个读成字节,分别按 ASCII 和 UTF-16 两种编码搜 longPathAware 这个字符串。
foreach ($f in 'C:\Windows\System32\cmd.exe', 'C:\Windows\System32\tar.exe', 'C:\Windows\explorer.exe') {
$bytes = [System.IO.File]::ReadAllBytes($f)
$a = [System.Text.Encoding]::ASCII.GetString($bytes).Contains('longPathAware')
$x = [System.Text.Encoding]::Unicode.GetString($bytes).Contains('longPathAware')
('{0} size={1} ascii={2} utf16={3}' -f (Split-Path $f -Leaf), $bytes.Length, $a, $x)
}这台机器上的结果:cmd.exe(289,792 字节)、powershell.exe(455,680 字节)、powershell_ise.exe(212,992 字节)三个能搜到这个字样;explorer.exe(6,089,584 字节)、notepad.exe(200,704 字节)、tar.exe(54,784 字节)、robocopy.exe(172,544 字节)、msiexec.exe(69,632 字节)搜不到。
必须说清这个粗测的边界:它只能证明"这段 XML 里出现过这个字符串",不能证明运行时行为,何况这台机器的注册表值还是 0,本来就测不出效果。它的实际用途只有一个——在自己动手改注册表之前,先对"改了大概管用到哪些程序"有个数量级判断。
三条解法怎么选?先看这张表
| 做法 | 要不要管理员 | 改不改文件位置 | 谁能看见这个短入口 | 什么时候用它 |
|---|---|---|---|---|
| subst 映射盘符 | 不要 | 不改 | 这台机器当前登录会话里的程序 | 只想临时取一个文件出来,或删掉里面几个文件 |
| 目录联接 mklink /J | 不要 | 不改 | 这台机器上所有走普通路径的程序 | 某个深层目录要长期进出,或者程序只让你选文件夹 |
| robocopy 搬到短根 | 不要 | 要 | 永久,且跟着文件走 | 这棵树要长期维护、要交给别人、要进版本库 |
| 扩展长度前缀 | 不要 | 不改 | 只有那次调用 | 写脚本时复核"到底存不存在",或一次性清掉超限树 |
| 注册表加应用清单 | 要 | 不改 | 只覆盖声明过的应用 | 自己开发程序,或确认目标软件已经声明 |
按这个顺序走基本不会错。
第一步,先量一下到底有多长(下一节那段脚本),知道自己是 250 还是 350,决定要不要搬。第二步,一次性需求一律用 subst,两分钟的事,不留痕迹。第三步,反复进出的目录做联接,入口是个普通文件夹,程序兼容性最好。第四步,只要涉及"发给别人"或"以后还要用",就直接搬到短根,别留一个只有你机器上有效的入口。第五步,删除卡住回到上一节的三条清理法。
怎么让路径一开始就不超过 260?
先解决"看不见有多长"。下面这段脚本实测可用,它按长度倒序列出目录树里最长的几个文件路径——注意它用的是扩展长度前缀,所以能把普通枚举看不见的那些也数进来:
$root = 'D:\tmp\l50\t11'
$u = '\\?\' + $root
[System.IO.Directory]::GetFiles($u, '*', [System.IO.SearchOption]::AllDirectories) |
ForEach-Object { $_.Substring(4) } |
Sort-Object { $_.Length } -Descending |
Select-Object -First 5 |
ForEach-Object { '{0} 字符 {1}' -f $_.Length, $_ }在这棵测试树上的实机输出:
331 字符 D:\tmp\l50\t11\xxxx……\zzzz……\ffffffffffffffffffffffffffffffffffffffff.txt把 $root 换成自己的目录即可。想先看哪个盘最深,就逐个目录跑一遍。算某一个目录还剩多少预算给名字,不用跑脚本也能算:259 减去这条目录路径的长度,再减去一个分隔符。实测那棵 247 字符的目录只剩 11 个字符,而一个 .txt 就吃掉 4 个——往里写文件时,含扩展名一共 5、10、11 个字符都能成,12 个字符就开始报"未能找到路径……的一部分。"。
知道长度之后,剩下的是几个习惯,都是围绕"整条字符串"而不是"文件名"来的:
层级比名字更值钱。 每一层目录名省 5 个字符,十层就是 50 个字符。真正吃掉预算的往往是中间那几层"某某项目 2026 年度 归档 第一批次"式的长目录,而不是最后的文件名。命名与归档的完整取舍在别再建"新建文件夹(2)"了:一套能让你 3 秒定位任何历史文件的命名与归档法里,本文只强调其中一条:给会往下套很多层的容器目录起短名。
根目录选浅的。 D:\work\ 和 D:\公司文件\2026 年度\项目资料备份\ 起步,一个给你留下两百多字符的预算,一个开局就吃掉一半。项目根、解压临时目录都放短根,这一条比任何技巧都有效。
解压前先改目标。 看到压缩包打算"解压到当前文件夹"时,先把目标改成一个短根下的新目录,再往里挪。第一节的实测说明:目标目录本身 131 个字符,配一个包内层级 194 个字符的压缩包,就是 351 个字符的灾难。想先看包里都有什么名字,本站的ZIP 打包与解压能把包内条目名列出来。
别把工具链的临时目录留在深层。 备份软件、截图工具、浏览器下载目录这些会自动追加日期或长任务名的地方,是"名字看起来不长、路径实际很长"的主要来源。资源管理器侧的习惯(包括怎么快速看到完整路径)在Windows 文件资源管理器效率指南里;要在整块盘上先把过深的目录揪出来,站内搜索里那篇Everything:让 Windows 文件搜索快到飞起的神器可以替代一层层点进去的做法。
常见问题
只是打不开一个文件,把文件名改短一点行不行?
分两种情况,都实测过。祖先目录自己还没超限时,改名字真有用:那棵最深目录 247 字符的树里,留给名字的预算是 259 减 247 再减 1,等于 11 个字符。往里直接写文件,名字 5、10、11 个字符(含扩展名)三次都成功,普通路径也读得到;12 和 13 个字符直接失败,报"未能找到路径……的一部分。"。也就是说那个 44 字符的文件名,砍到 11 个字符以内就够得着。但只要上一层本身过了线,改名字一点用也没有:再往下叠一层,让父目录长到 285 个字符,往里放一个名字只有 6 个字符的文件(整条 291),普通 Test-Path 返回 False、Get-Content 读不到,带扩展长度前缀照样能读出内容。所以改名字只治"差一点点",还会破坏软件原本记着的路径引用;真正的长路径问题,解法还是给一个短入口或者整棵树搬到短根。
我已经把 LongPathsEnabled 设成 1 了,为什么资源管理器还是不行?
因为官方要求的是两件事一起满足:"必须设置注册表值,应用程序清单必须包含 longPathAware 元素。"注册表只管一半。另一半在软件开发者那边,官方还专门强调"这不是将影响所有应用程序的更改"。另外还有一条会让人误判"改了没用"的机制:这个值"在首次调用受影响的 Win32 文件或目录函数后,系统(每个进程)将缓存此注册表值……在进程的生存期内,将不会重新加载",官方明确说为了让所有应用识别,"可能需要重新启动"。所以顺序是:改值、重启、再测;而某个具体程序到底受不受影响,取决于它自己有没有声明。
双击提示"找不到该文件",是不是文件其实被删了?
先复核再下结论。同一台机器上,普通路径的 Test-Path 返回 False、Get-Content 报"找不到路径……因为该路径不存在",而带 \\?\ 前缀能正常读出内容——这不是文件丢了,是那条路径普通接口读不到。最快的验证是在它的父目录里列一次文件(实测 247 字符的父目录能列出 292 字符的文件名),或者用前缀跑一次 Test-Path。复核完确实不存在,再回到回收站、同步盘的版本历史那条线上去找。
压缩软件明明显示解压成功,文件却看不见,是软件坏了吗?
不是。实测把同一个包分别交给系统自带的 tar.exe(bsdtar 3.5.2)和 7z x(NanaZip 7.0)解压,两者退出码都是 0,后者的输出里还写着"Everything is Ok",但落点路径 351 个字符,普通接口看不见、前缀读得到。所以"退出码 0"只说明解压动作本身没出错,不说明结果可达。处理办法:换一个短目标目录重解一次,这是最快的一条;已经解出来的,用 subst 或联接进去把它拷到短根,再删掉原来那棵。
命令行敲 tar 报"Cannot connect to D: resolve failed",是 Windows 不支持长路径吗?
不是,这一句和 260 没关系。这台机器上直接敲 tar 命中的是 Git 自带的 GNU tar 1.35,它不认 D:\ 这种写法,所以报"无法连接 D:",退出码 128。改成写全路径调用系统自带的那个——C:\Windows\System32\tar.exe(实测 bsdtar 3.5.2、libarchive 3.5.2)——同样的命令退出码 0。排查顺序:先看 tar 到底解析到哪个文件,再谈它支不支持长路径。
映射盘符和目录联接会不会把文件复制一份,占双倍空间?
都不会。subst 只是把一个已存在的路径挂到一个盘符上,官方定义就是"Associates a path with a drive letter";联接是一个重分析点(实测属性为 Directory, ReparsePoint),本身只占一个目录项。撤掉时也不碰内容:subst S: /d 之后目标目录 Test-Path 仍为 True,rd 删掉联接后目标里的内容一项没少(见前面那张表,三次测试的目标分别是 1 项、3 项、1 项)。真正会占双倍空间的是复制搬运,那是另一条解法。
网络共享上的长路径也能用这个前缀吗?
官方给了对应写法:扩展长度前缀也可以用于 UNC 路径,但要写成 \\?\UNC\server\share,其中 server 是计算机名、share 是共享文件夹名(微软文档:最大路径长度限制)。要注意前缀的通用限制照样适用:不解析 . 和 ..,分隔符只能是反斜杠,也不能写相对路径。本文的实测都在本地磁盘上做,共享上的行为没有覆盖。
最后三件事
第一,看到"路径太长"类的报错,先分清是哪一句。带"260""248"两个数字的那句最诚实;"未能找到路径……的一部分"和"因为该路径不存在"这两句也大概率是同一个原因,别去反复检查文件名。
第二,解法按"要不要留痕"选:一次性用 subst,长期用目录联接,要交付或协作就直接用 robocopy 搬到短根。三条都不需要管理员权限,也都实测通过。
第三,永远别把 rd /s /q 的退出码当删除成功的证据。这台机器上它删超限目录返回 0、树里还剩 9 项。要确认,就再数一遍——而且要用带 \\?\ 前缀的方式去数,否则你数的正是那套会漏的接口。