ichuan.net

自信打不死的心态活到老

windows下路径长度限制问题

问题

昨日志旭在做一个测试时发现在 windows 下无法删除一个目录,报错如下:

enter image description here

在资源管理器左侧展开查看,发现这是个嵌套很深的目录:

enter image description here

底部红色标注的目录其实还有子目录,但是展示不出来了。在 cmd.exe 下用 del /S /Q e:\test 或者在 powershell 中用 Remove-Item e:\test -recurse 同样无法删除改目录。

原因

查了半天,原来是 windows 的路径长度限制问题导致的。据说 ANSI 版本的 windows API 能操作的最大路径长度是 260 个字符,但我在本机测试时,这个值是 247:

>>> import os
>>> x = 'e:\\'
>>> x += 'e' * (247 - len(x))
>>> len(x)
247
>>> os.mkdir(x)
>>> os.mkdir(x + 'e')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: [Error 206] : 'e:\\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
>>>

windows 下的 python 文件操作也是调用的系统 API,所以是受限制的。那之前的深层目录是如何创建的呢?原来是一个 java 程序创建的。java 程序是原生支持长路径名的。

解决方法

根据微软官方文档,要操作大于 260 字符的路径名,就需要使用 Unicode 版本的 windows API,那个限制是 32767,近乎无限制了。并且,需要在目录名前面加特殊字母:\\?\

我们先来重现一个无法在资源管理器和普通 python 代码里删除的深层目录。需注意两点:

  1. 使用 ctypes 里导出的 windll.kernel32 直接调用 Unicode 版本的创建目录 API:CreateDirectoryW
  2. 目录名前加特殊标识:\\?\

代码如下:

import os
import ctypes

mkdir = ctypes.windll.kernel32.CreateDirectoryW

cur = u'\\\\?\\e:\\'

for i in xrange(100):
    cur += u'test\\'
    mkdir(cur, None)

执行后会在 E 盘创建一个删不掉的 test 文件夹。如果你把上面的 mkdir 替换成 os.mkdir,会发现报错。

接下来尝试删除刚创建的嵌套目录。除了需要加特殊标识,还有个要注意的是:windows 下没有 API 提供类似 rm -rf 的功能,只有删除单个文件和删除空目录的 API,我们需要自己实现一个 rm -rf

import os, ctypes

rmdir = ctypes.windll.kernel32.RemoveDirectoryW

def rm_rf(dirname):
    for i in os.listdir(dirname):
        j = os.path.join(dirname, i)
        if os.path.isfile(j):
            os.remove(j)
        else:
            rm_rf(j)
    rmdir(dirname)

rm_rf(u'\\\\?\\e:\\test\\')

执行后会发现刚才的顽固目录被删除了。同样的,如果你把上面的 rmdir 换成 os.rmdir,也会发现报错。

你发现没?上述代码里的 os.listdiros.path.isfileos.path.join 都没换成 windows Unicode API,但也没问题,何解?原来部分 os 模块的接口是支持 unicode 文件名的:

>>> os.path.supports_unicode_filenames
True

但可惜的是 os.rmdir 不支持,不然就不用费心转用 windows API 了。

其他

linux 下对目录长度有限制吗?据说也有:linux 下目录长度限制是 4096 字节,文件名长度限制是 255 字节。

Comments