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

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

底部红色标注的目录其实还有子目录,但是展示不出来了。在 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 代码里删除的深层目录。需注意两点:
- 使用
ctypes里导出的windll.kernel32直接调用 Unicode 版本的创建目录 API:CreateDirectoryW - 目录名前加特殊标识:
\\?\
代码如下:
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.listdir、os.path.isfile、os.path.join 都没换成 windows Unicode API,但也没问题,何解?原来部分 os 模块的接口是支持 unicode 文件名的:
>>> os.path.supports_unicode_filenames
True
但可惜的是 os.rmdir 不支持,不然就不用费心转用 windows API 了。
其他
linux 下对目录长度有限制吗?据说也有:linux 下目录长度限制是 4096 字节,文件名长度限制是 255 字节。