快速递归文件夹删除 - 调用正确的rmdir

4 投票
4 回答
2893 浏览
提问于 2025-04-18 15:39

这个问题和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 个回答

0

根据这里的说明,rmdir 这个命令似乎有一个别名,叫做 rd。我没法亲自测试,但你可以试试看。

>>> args = ['rd', r'D:\tmp', '/s', '/q']
>>> subprocess.call(args)

在删除隐藏文件或系统文件时,可能会有一些限制——我同样没法测试这个。

1

是的,我找到了这个别名,但还是有同样的问题……如果有人创建了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的权限比较弱,但其实绕过这些权限并不难。

2

使用内置的 os.walkos.removeos.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 来清理空的目录。不过,这种方法可能会让磁盘系统负担过重。

2

调用正确的 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() 慢,可能是因为涉及到了用户界面)。

撰写回答