快速递归文件夹删除 - 调用正确的rmdir
这个问题和python没有直接关系,但我需要在windows下的python32中实现一个可用的方案。
从这个回答开始,我发现使用shutil.rmtree()
在windows上真的很慢(我每天需要删除超过300万个文件,结果花费超过24小时),所以我想用subprocess.call()
和rmdir
来解决这个问题。但是因为我的系统变量%PATH%
中有cygwin,错误的rmdir
会被调用,结果我会得到这样的错误:
>>> args = ['rmdir', r'D:\tmp']
>>> subprocess.call(args)
cygwin warning:
MS-DOS style path detected: D:\tmp
Preferred POSIX equivalent is: /cygdrive/d/tmp
CYGWIN environment variable option "nodosfilewarning" turns off this warning.
Consult the user's guide for more details about POSIX paths:
http://cygwin.com/cygwin-ug-net/using.html#using-pathnames
rmdir: failed to remove `D:\\tmp': Directory not empty
1
注意:我知道要递归删除文件夹需要使用/S /Q
。
我该如何确保调用正确的rmdir
(就像在linux中你会使用绝对路径 - /bin/rm
)呢?最好是不使用shell=True
。
有没有其他的工具可以做到这一点(类似于使用robocopy /MIR
)?
编辑:速度比较
我测试了不同的方法来删除237 GB (255,007,568,228 字节)的文件,这些文件包含1,257,449个文件,750,251个文件夹,使用了Measure-Command
。
+-------------------+-------------+----------+-----------------+
| | rmdir /s /q | shutil | SHFileOperation |
+-------------------+-------------+----------+-----------------+
| Hours | 3 | 5 | 6 |
| Minutes | 26 | 52 | 14 |
| Seconds | 46 | 13 | 48 |
| TotalMinutes | 207 | 352 | 375 |
| TotalSeconds | 12406 | 21134 | 22488 |
| TotalMilliseconds | 12406040 | 21133805 | 22488436 |
+-------------------+-------------+----------+-----------------+
注意:测试是在生产服务器上进行的(所以结果可能会受到影响)
4 个回答
根据这里的说明,rmdir
这个命令似乎有一个别名,叫做 rd
。我没法亲自测试,但你可以试试看。
>>> args = ['rd', r'D:\tmp', '/s', '/q']
>>> subprocess.call(args)
在删除隐藏文件或系统文件时,可能会有一些限制——我同样没法测试这个。
是的,我找到了这个别名,但还是有同样的问题……如果有人创建了rd.exe(或者在PATH变量的任何地方安装了它),那就不行了。其实在这种情况下没什么关系,因为关键是会调用无效的rmdir(就是来自cygwin的那个),我不想写一个依赖于没有人会在进程的当前工作目录下创建rmdir.exe的代码。
那么,问题是“在路径的任何地方”还是当前工作目录呢?如果是当前工作目录,那:
if os.path.exists('rmdir.exe'):
raise BadPathError("don't run this in an insecure directory")
但根本的问题是,你允许这个从一个可以创建rmdir.exe
的目录运行。是的,Windows的权限比较弱,但其实绕过这些权限并不难。
使用内置的 os.walk
、os.remove
和 os.rmdir
函数
需要特别注意的是 Windows 的路径问题。你可以用 /
来代替路径中的 \
,或者使用原始字符串。
不过,最好还是对从命令行获取的路径名使用 os.path.normpath
来规范化一下。
接下来代码中的 topdown=False
是非常重要的。
path = os.path.normpath(path)
for root, dirs, files in os.walk(path, topdown=False):
for f in files:
os.remove(os.path.join(root, f))
for d in dirs:
os.rmdir(os.path.join(root, d))
一个可能的提速方法是先把所有文件路径收集到一个列表中,然后用 multiprocessing.Pool.map()
来利用多个进程删除这些文件。之后,你可以用 os.removedirs
来清理空的目录。不过,这种方法可能会让磁盘系统负担过重。
调用正确的 rmdir
我想到了一个方法,就是直接从 %SYSTEMROOT%\System32
手动调用 cmd.exe /C
,然后清除 env
变量(看起来是有效的):
def native_rmdir(path):
''' Removes directory recursively using native rmdir command
'''
# Get path to cmd
try:
cmd_path = native_rmdir._cmd_path
except AttributeError:
cmd_path = os.path.join(
os.environ['SYSTEMROOT'] if 'SYSTEMROOT' in os.environ else r'C:\Windows',
'System32', 'cmd.exe')
native_rmdir._cmd_path = cmd_path
# /C - cmd will terminate after command is carried out
# /S - recursively,
args = [cmd_path, '/C', 'rmdir', '/S', '/Q', path]
subprocess.check_call(args, env={})
native_rmdir(r'D:\tmp\work with spaces')
我认为这个方法在任何版本的Windows上都能用,不管系统的 PATH
设置如何,但我还是希望能有更 “优雅” 的解决方案。
这个方法会删除它能找到的所有文件(它不会在第一个错误后停止)。
使用 SHFileOperation()
也可以使用 SHFileOperation()
来实现这个功能 [示例源代码]:
from win32com.shell import shell, shellcon
shell.SHFileOperation((0, shellcon.FO_DELETE, r'D:\tmp\del', None, shellcon.FOF_NO_UI))
这个方法会在第一个错误后停止(我在我的环境中测试时发现这个方法通常比 shutil.rmtree()
慢,可能是因为涉及到了用户界面)。