shutil.rmtree在Windows上失败并显示“拒绝访问”的错误

93

在Python中,当运行shutil.rmtree删除一个包含只读文件的文件夹时,会打印以下异常:

 File "C:\Python26\lib\shutil.py", line 216, in rmtree
   rmtree(fullname, ignore_errors, onerror)
 File "C:\Python26\lib\shutil.py", line 216, in rmtree
   rmtree(fullname, ignore_errors, onerror)
 File "C:\Python26\lib\shutil.py", line 216, in rmtree
   rmtree(fullname, ignore_errors, onerror)
 File "C:\Python26\lib\shutil.py", line 216, in rmtree
   rmtree(fullname, ignore_errors, onerror)
 File "C:\Python26\lib\shutil.py", line 216, in rmtree
   rmtree(fullname, ignore_errors, onerror)
 File "C:\Python26\lib\shutil.py", line 216, in rmtree
   rmtree(fullname, ignore_errors, onerror)
 File "C:\Python26\lib\shutil.py", line 216, in rmtree
   rmtree(fullname, ignore_errors, onerror)
 File "C:\Python26\lib\shutil.py", line 221, in rmtree
   onerror(os.remove, fullname, sys.exc_info())
 File "C:\Python26\lib\shutil.py", line 219, in rmtree
   os.remove(fullname)
WindowsError: [Error 5] Access is denied: 'build\\tcl\\tcl8.5\\msgs\\af.msg'

在文件属性对话框中查看时,我注意到af.msg文件被设置为只读。

那么问题来了:最简单的解决方法是什么 - 假设我的意图是在Windows上执行与rm -rf build/相当的操作?(不使用像unxutils或cygwin这样的第三方工具-因为此代码旨在在安装有Python 2.6 w / PyWin32的纯Windows上运行)


5
shutil.rmtree 使用 os.remove 来删除文件。os.remove 可以成功删除只读文件(至少在 Unix 上是这样)。如果文件正在使用中,则 os.remove 无法在 Windows 上删除该文件。 - jfs
根据我的经验,如果目录是打开状态并且您运行了代码,那么可能会出现此错误,这与删除过程有关,而不是创建步骤。 - Ali_Sh
5个回答

118

看看这个问题:Python脚本在Windows下以什么用户身份运行?

显然,答案是将文件/文件夹更改为非只读,然后删除它。

这里是pathutils.py中@Sridhar Ratnakumar在评论中提到的onerror()处理程序:

def onerror(func, path, exc_info):
    """
    Error handler for ``shutil.rmtree``.

    If the error is due to an access error (read only file)
    it attempts to add write permission and then retries.

    If the error is for another reason it re-raises the error.
    
    Usage : ``shutil.rmtree(path, onerror=onerror)``
    """
    import stat
    # Is the error an access error?
    if not os.access(path, os.W_OK):
        os.chmod(path, stat.S_IWUSR)
        func(path)
    else:
        raise

1
哈哈,我刚刚在http://www.voidspace.org.uk/downloads/pathutils.py发现了`onerror`处理程序。 - Sridhar Ratnakumar
发现通过http://trac.pythonpaste.org/pythonpaste/ticket/359。 - Sridhar Ratnakumar
2
尽管此答案的评论中指出“更改文件/文件夹以使其不是只读”,但我仍然在只读文件夹上收到了访问被拒绝的消息。然而,这个 实现起作用了。 - Pakman
3
警告:如果您想直接复制粘贴这个函数,请将 import stat 放在函数外面。如果您将导入语句留在函数内并且该函数位于类的 __del__ 方法中,就会出现 RuntimeError: sys.meta_path must be a list of import hooks 错误。请注意。 - Adam
3
解决方案中的“else raise”部分不会引发异常。来自Python文档:“onerror引发的异常不会被捕获。” https://docs.python.org/2/library/shutil.html#shutil.rmtree - GDICommander

40

我建议使用os.walk实现您自己的rmtree,它通过在尝试删除文件之前对每个文件使用os.chmod来确保访问权限。

可以尝试以下代码(未经测试):

import os
import stat

def rmtree(top):
    for root, dirs, files in os.walk(top, topdown=False):
        for name in files:
            filename = os.path.join(root, name)
            os.chmod(filename, stat.S_IWUSR)
            os.remove(filename)
        for name in dirs:
            os.rmdir(os.path.join(root, name))
    os.rmdir(top)      

1
这几乎是正确的 - Windows仅支持stat.S_IWRITE(这正是您想要的)- http://docs.python.org/library/os.html#os.chmod - Daniel G
1
我测试了os.chmod(filename, stat.S_IWUSR),它可以移除只读标志,因此在WinXP上可以正常工作。考虑到文档对于stat.S_IWRITE的描述是"Unix V7的S_IWUSR同义词" (http://docs.python.org/library/stat.html#stat.S_IWRITE),我认为我的代码是正确的。 - Epcylon
太好了,对于文件路径过长的情况,这似乎是唯一的方法。建议考虑提交或更改shutil.rmtree。 - Anthony
使用此函数时要非常小心。该函数会进入每个子文件夹并删除其中的文件。 - Maryam Bahrami
玛丽亚姆·巴赫拉米:是的,这就是rmtree函数的目的。如果这不是你想要的,那么你为什么要使用它呢? - Epcylon
显示剩余3条评论

25

好的,被标记为答案的解决方案对我没有用...我采用了以下方法:

os.system('rmdir /S /Q "{}"'.format(directory))

这将删除目录本身。您能告诉我如何删除目录中的所有文件和子目录吗?例如,如果我给出路径:**myproject/dir1/**,那么它会删除dir1,但我想删除dir1下的所有内容。 - Helping Hands
就我个人而言,我觉得直接删除目录然后重新创建会更容易一些(虽然这样做会丢失时间戳)。 - Mawg says reinstate Monica

2
shutil.rmtree(path,ignore_errors=False,onerror=errorRemoveReadonly) 
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

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

-6
如果您使用 cygwin 运行脚本,则可以使用 subprocess.call
from subprocess import call
call("rm -rf build/", shell=True)

当然,它只能在cygwin/bash模拟器内运行。


2
在Windows上调用rm -rf?我可不这么认为。 - omerfarukdogan
非常奇怪。我使用一个类Unix的Windows控制台模拟器(cmder)。当我从那个控制台运行脚本时,subprocess.call方法有效,但如果我从默认的“命令提示符”运行它,则无效。 - Dustin Michels
你试过在点踩之前尝试吗?我确认它在Windows下可以工作。 - besil
1
@besil,是的,call('rm -rf "C:\\Temp\\tmp7cm15k\\"', shell=True) 会导致 'rm' 不是内部或外部命令、可执行程序或批处理文件。 - omerfarukdogan
1
嗯,我认为这对我有效,因为我使用Cygwin作为终端仿真器,而不是命令提示符。 - besil
显示剩余2条评论

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