如何删除非空文件夹?

1103

当我尝试删除一个非空文件夹时,出现了“拒绝访问”的错误。在我的尝试中,我使用了以下命令:os.remove("/folder_name")

如何最有效地删除一个非空的文件夹/目录?


37
请注意,即使目录为空,os.remove再次执行仍将失败,因为正确的函数是os.rmdir。 - tzot
1
有关特定的 rm -rf 行为,请参见:https://dev59.com/IHRA5IYBdhLWcg3wzhfZ - Ciro Santilli OurBigBook.com
24个回答

1762
import shutil

shutil.rmtree('/folder_name')

标准库参考:shutil.rmtree

rmtree由设计上不适用于包含只读文件的文件夹树。如果您想删除该文件夹而不管它是否包含只读文件,请使用以下方法:

shutil.rmtree('/folder_name', ignore_errors=True)

84
请注意,如果存在只读文件,则 rmtree 将失败:https://dev59.com/wnE85IYBdhLWcg3wr1qx。 - Sridhar Ratnakumar
12
这对我没用:Traceback (most recent call last): File "foo.py",第31行,在<module>中: shutil.rmtree(thistestdir) File "/usr/lib/python2.6/shutil.py",第225行,在rmtree中: onerror(os.rmdir, path, sys.exc_info()) File "/usr/lib/python2.6/shutil.py",第223行,在rmtree中: os.rmdir(path) OSError:[Errno 90] 目录非空:'/path/to/rmtree' - Clayton Hughes
4
克莱顿:很可能在rmtree正在删除文件时同时添加了一个文件,“rm -rf”也会失败。 - ddaa
17
有人知道为什么这个功能不在os包中吗?看起来os.rmdir函数相当无用。有没有好的理由解释为什么是这样实现的? - Malcolm
24
@Malcolm 这个包是对操作系统功能的封装。在POSIX系统中,如果目录不为空,则rmdir将失败。 UbuntuWindows是此方面符合POSIX标准的流行示例。 - Iain Samuel McLean Elder
显示剩余4条评论

162

来自Python文档关于os.walk()函数的说明:

# Delete everything reachable from the directory named in 'top',
# assuming there are no symbolic links.
# CAUTION:  This is dangerous!  For example, if top == '/', it
# could delete all your disk files.
import os
for root, dirs, files in os.walk(top, topdown=False):
    for name in files:
        os.remove(os.path.join(root, name))
    for name in dirs:
        os.rmdir(os.path.join(root, name))

1
也许我错了,但是现在我觉得这样做是正确的。 - ddaa
4
使用shutil绝对是最简单的方法,但这个解决方案并不违反Python风格。我之前可能不会点赞这个回答,但这一次我为了抵消你的踩赞了 :) - Jeremy Cantrell
8
这段话的意思是:这段代码本身符合Pythonic风格。在真实的程序中使用它代替shutil.rmtree会不符合Pythonic风格:这将忽略“一种明显的做法”。无论如何,这只是语义问题,移除downmod。 - ddaa
2
@ddaa 想要记录每个被删除的文件或目录是否不符合 Python 的风格?我不确定如何使用 shutil.rmtree 实现这一点。 - Jonathan Komar
4
这是一个值得思考的话题,即修辞学。我知道自己在做什么。我只是想让你重新考虑“显而易见的方法”,并提供一个原因,说明shutil.rmtree可能不是最合适的选择。 - Jonathan Komar
显示剩余4条评论

144
import shutil
shutil.rmtree(dest, ignore_errors=True)

1
这是正确的答案。在我的系统中,即使我将文件夹中的所有内容都设置为读写模式,但当我尝试删除时仍然会出现错误。 ignore_errors=True 可以解决这个问题。 - Aventinus
4
我的答案中使用了 onerror 参数而不是 ignore_errors。这样只读文件将被删除而不是被忽略。 - Dave Chandler
是的,这不会在出现错误时删除文件。因此基本上整个“rmtree()”方法被忽略了。 - Juha Untinen
3
这应该只是对之前接受的答案进行一次小修改,而非新的回答。我现在会这样做。 - Jean-François Corbett

41

从Python 3.4开始,您可以使用:

import pathlib

def delete_folder(pth):
    for sub in pth.iterdir():
        if sub.is_dir():
            delete_folder(sub)
        else:
            sub.unlink()
    pth.rmdir() # if you just want to delete the dir content but not the dir itself, remove this line

其中pth是一个 pathlib.Path 实例。不错,但可能不是最快的。


24

来自docs.python.org

This example shows how to remove a directory tree on Windows where some of the files have their read-only bit set. It uses the onerror callback to clear the readonly bit and reattempt the remove. Any subsequent failure will propagate.

import os, stat
import shutil

def remove_readonly(func, path, _):
    "Clear the readonly bit and reattempt the removal"
    os.chmod(path, stat.S_IWRITE)
    func(path)

shutil.rmtree(directory, onerror=remove_readonly)

12

根据kkubasik的回答,在删除之前检查文件夹是否存在,更加健壮可靠。

import shutil
def remove_folder(path):
    # check if folder exists
    if os.path.exists(path):
         # remove if exists
         shutil.rmtree(path)
    else:
         # throw your exception to handle this special scenario
         raise XXError("your exception") 
remove_folder("/folder_name")

8
这可能会引入竞态条件。 - Corey Goldberg
1
根据最Pythonic的删除可能不存在的文件的方法,最好使用try删除并处理except,而不是先调用exists() - TT--

9
import os
import stat
import shutil

def errorRemoveReadonly(func, path, exc):
    excvalue = exc[1]
    if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
        # change the file to be readable,writable,executable: 0777
        os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)  
        # retry
        func(path)
    else:
        # raiseenter code here

shutil.rmtree(path, ignore_errors=False, onerror=errorRemoveReadonly) 

如果设置了ignore_errors,错误将被忽略;否则,如果设置了onerror,将使用(func, path, exc_info)作为参数来处理错误,其中func是os.listdir、os.remove或os.rmdir;path是导致函数失败的参数;exc_info是由sys.exc_info()返回的元组。如果ignore_errors为假且onerror为None,则会引发异常。


根据文档,_onerror引发的异常不会被捕获_,所以我不确定你的_raise enter code here_有什么意义。 - kmarsh
1
-1. 相比Dave Chandler的答案,这似乎过于复杂了。而且,如果我们想要移除readonly,我们不需要将文件设为可执行。 - idbrii

8
我想添加一个 "纯`pathlib`"方法:
from pathlib import Path
from typing import Union

def del_dir(target: Union[Path, str], only_if_empty: bool = False):
    """
    Delete a given directory and its subdirectories.

    :param target: The directory to delete
    :param only_if_empty: Raise RuntimeError if any file is found in the tree
    """
    target = Path(target).expanduser()
    assert target.is_dir()
    for p in sorted(target.glob('**/*'), reverse=True):
        if not p.exists():
            continue
        p.chmod(0o666)
        if p.is_dir():
            p.rmdir()
        else:
            if only_if_empty:
                raise RuntimeError(f'{p.parent} is not empty!')
            p.unlink()
    target.rmdir()

这取决于 Path 可以排序,较长的路径总是在较短的路径之后排序,就像 str 一样。因此,目录将排在文件之前。如果我们反转排序,那么文件将排在它们各自的容器之前,所以我们可以通过一个遍历单次 unlink/rmdir 它们。
好处:
  • 它不依赖外部二进制文件:所有内容都使用 Python 自带的模块(Python >=3.6)
    • 这意味着它不需要不断启动新的子进程来进行 unlink 操作
  • 它非常快速简单;您不必实现自己的递归
  • 它跨平台(至少在 Python 3.6 中,pathlib 所承诺的是如此;没有操作声明无法在 Windows 上运行)
  • 如果需要,可以进行非常精细的日志记录,例如记录每个删除操作的发生情况。

你能提供一个使用示例吗,例如 del_dir(Path()) 吗?谢谢。 - lcapra
@lcapra 只需将要删除的目录作为第一个参数调用即可。 - pepoluan
1
它快速且内存高效:没有递归堆栈,也不需要启动子进程 - 实际上并非如此。在递归全局搜索中仍然存在递归。它也不是内存高效的,因为您会生成两个包含所有文件和文件夹路径的列表:sorted内置首先生成由glob生成器返回的项目列表,然后生成一个新列表,其中包含排序的项目。根据文件数量的不同,这可能导致显着的内存消耗。哦,还有你引入了一个时间复杂度为n log n的排序。 - danzel
@danzel 你在技术上是正确的。我会编辑我的回答,以免误导。 - pepoluan
@danzel 话说,我认为排序不会比重复启动子进程来使用 os.systemsubprocess.run 运行 shell 命令更慢。此外,维护一个列表和一个已排序的列表所需的内存可能比启动并运行子进程所需的内存更小。你的情况可能有所不同。 - pepoluan

7
如果你确定要删除整个目录树,并且不再关心目录的内容,那么遍历整个目录树是愚蠢的...只需从Python中调用本机操作系统命令即可。这样更快、更有效率,占用的内存也更少。
RMDIR c:\blah /s /q 

或*nix
rm -rf /home/whatever 

在Python中,代码会像这样..
import sys
import os

mswindows = (sys.platform == "win32")

def getstatusoutput(cmd):
    """Return (status, output) of executing cmd in a shell."""
    if not mswindows:
        return commands.getstatusoutput(cmd)
    pipe = os.popen(cmd + ' 2>&1', 'r')
    text = pipe.read()
    sts = pipe.close()
    if sts is None: sts = 0
    if text[-1:] == '\n': text = text[:-1]
    return sts, text


def deleteDir(path):
    """deletes the path entirely"""
    if mswindows: 
        cmd = "RMDIR "+ path +" /s /q"
    else:
        cmd = "rm -rf "+path
    result = getstatusoutput(cmd)
    if(result[0]!=0):
        raise RuntimeError(result[1])

35
shutil.rmdir 的整个目的就是让你与操作系统的类型隔离开来。 - mtrw
4
我了解这个概念,但是当人们非常清楚地知道他们想要完全删除文件夹时,遍历整个文件树有什么意义呢?shutil.rmdir特别调用os.listdir()、os.path.islink()等等一些检查,这些检查并不总是必要的,因为所有需要的只是取消链接文件系统节点。此外,在某些构建系统上,例如MSWindows用于MSAuto/WinCE开发,shutil.rmdir几乎总是会失败,因为MSAuto基于批处理的开发在失败退出时锁定了一些奇怪的构建文件,而只有rmdir /S/Q或重启才有帮助来清理它们。 - P M
2
没错,只使用rm更接近内核,使用更少的时间、内存和CPU...就像我所说的那样,我使用这种方法的原因是由MSAuto批处理构建脚本留下的锁定。 - P M
3
是的,但使用shutil可以使代码跨平台,并抽象出平台细节。 - xshoppyx
2
我认为这个答案不应该被投票降至1以下,因为它为某些情况下的解决方法提供了一个非常好的参考,读者可能会对此感兴趣。我喜欢有多种方法并按顺序排名的帖子。所以即使我不需要使用它,我现在知道它可以做到以及如何做到。 - kmcguire
显示剩余3条评论

6

这里列出一些Python 3.5的选项,以补充上面回答中可能遗漏的内容(我希望能够在此找到它们)。

import os
import shutil
from send2trash import send2trash # (shutil delete permanently)

如果文件夹为空,则删除文件夹

root = r"C:\Users\Me\Desktop\test"   
for dir, subdirs, files in os.walk(root):   
    if subdirs == [] and files == []:
           send2trash(dir)
           print(dir, ": folder removed")

如果文件夹中包含此文件,则同时删除文件夹

    elif subdirs == [] and len(files) == 1: # if contains no sub folder and only 1 file 
        if files[0]== "desktop.ini" or:  
            send2trash(dir)
            print(dir, ": folder removed")
        else:
            print(dir)

如果文件夹中只包含 .srt 或 .txt 文件,则删除该文件夹

    elif subdirs == []: #if dir doesn’t contains subdirectory
        ext = (".srt", ".txt")
        contains_other_ext=0
        for file in files:
            if not file.endswith(ext):  
                contains_other_ext=True
        if contains_other_ext== 0:
                send2trash(dir)
                print(dir, ": dir deleted")

如果文件夹大小小于400kb,则删除该文件夹:

def get_tree_size(path):
    """Return total size of files in given path and subdirs."""
    total = 0
    for entry in os.scandir(path):
        if entry.is_dir(follow_symlinks=False):
            total += get_tree_size(entry.path)
        else:
            total += entry.stat(follow_symlinks=False).st_size
    return total


for dir, subdirs, files in os.walk(root):   
    If get_tree_size(dir) < 400000:  # ≈ 400kb
        send2trash(dir)
    print(dir, "dir deleted")

4
请修正缩进并修改代码 if files[0]== "desktop.ini" or: 。 提示:修改后的代码应该还有语法错误。 - Mr_and_Mrs_D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接