因此,我正在构建一个简单的脚本,将某些文档备份到我的第二个硬盘(您永远不知道会发生什么!)因此,我使用shutil.copytree
函数在第二个驱动器上复制数据。它工作得很好,这不是问题所在。
如果目标已经存在,我将使用shutil.rmtree
函数移除树。我会给你看我的密码:
import shutil
import os
def overwrite(src, dest):
if(not os.path.exists(src)):
print(src, "does not exist, so nothing may be copied.")
return
if(os.path.exists(dest)):
shutil.rmtree(dest)
shutil.copytree(src, dest)
print(dest, "overwritten with data from", src)
print("")
overwrite(r"C:\Users\Centurion\Dropbox\Documents", r"D:\Backup\Dropbox Documents")
overwrite(r"C:\Users\Centurion\Pictures", r"D:\Backup\All Pictures")
print("Press ENTER to continue...")
input()
正如你所看到的,一个简单的脚本。现在,当我第一次运行这个脚本时,一切都很好。图片和文件复制到我的D:
驱动器很好。但是,当我第二次运行时,这是我的输出:
C:\Users\Centurion\Programming\Python>python cpdocsnpics.py
D:\Backup\Dropbox Documents overwritten with data from C:\Users\Centurion\Dropbox\Documents
Traceback (most recent call last):
File "cpdocsnpics.py", line 17, in <module>
overwrite(r"C:\Users\Centurion\Pictures", r"D:\Backup\All Pictures")
File "cpdocsnpics.py", line 10, in overwrite
shutil.rmtree(dest)
File "C:\Python34\lib\shutil.py", line 477, in rmtree
return _rmtree_unsafe(path, onerror)
File "C:\Python34\lib\shutil.py", line 376, in _rmtree_unsafe
onerror(os.rmdir, path, sys.exc_info())
File "C:\Python34\lib\shutil.py", line 374, in _rmtree_unsafe
os.rmdir(path)
PermissionError: [WinError 5] Access is denied: 'D:\\Backup\\All Pictures'
这个错误只在我第一次复制Pictures
时发生;我假设它与作为一个库有关。
我该怎么办?
发布于 2014-05-29 03:53:32
这是一个跨平台的一致性问题。您已经使用readonly
属性复制了文件/dirs。在第一次不存在"dest
“时,不执行rmtree方法。但是,当您尝试运行"overwrite“函数时,我们可以注意到存在"dest”位置(以及它的子树),但是它是通过只读访问复制的。所以我们遇到了一个问题。为了“修复”问题,您必须为onerror参数shutil.rmtree提供一个处理程序。只要您的问题是关于只读问题,解决办法就会有点像这样:
def readonly_handler(func, path, execinfo):
os.chmod(path, 128) #or os.chmod(path, stat.S_IWRITE) from "stat" module
func(path)
正如您在python中所看到的,onerror必须是可调用的,它接受三个参数:函数、路径和excinfo。欲了解更多信息,请访问读读医生。
def overwrite(src, dest):
if(not os.path.exists(src)):
print(src, "does not exist, so nothing may be copied.")
return
if(os.path.exists(dest)):
shutil.rmtree(dest, onerror=readonly_handler)
shutil.copytree(src, dest)
print(dest, "overwritten with data from", src)
print("")
当然,这个处理程序是简单而具体的,但是如果发生其他错误,将会引发新的异常,并且这个处理程序可能无法修复它们!
注释: Python (Python贡献者)一直在修补shutil.rmtree问题,它似乎将在Python3.5中得到解决(参见http://bugs.python.org/issue19643)。
发布于 2014-09-14 08:05:54
我发现了一个问题,而不是在Windows上使用shutil.rmtree
(在Windows7上进行测试)只读文件。我使用shutil.rmtree
和shutil.copytree
的组合在测试套件中创建测试夹具,因此在短时间内(<1秒间隔)重复调用序列,并且在测试套件中看到不可预测的失败部分,报告了EACCES和ENOTEMPTY错误。这些症状告诉我,shutil.rmtree
函数在返回调用程序时还没有完成,并且直到一段时间之后,被删除的文件名才可以重新使用。
TL;DR:这个解决方案不是很好--从广义上讲,它在删除目录之前重新命名它,但是有许多问题需要处理,因为Windows文件系统似乎需要一些时间才能赶上经过改进的操作。实际代码捕获各种故障条件,并在短时间延迟后重试失败操作的变体。
接下来将进行更长时间的讨论,最后是我的最后代码。
我的第一个想法是在删除目录树之前尝试重命名目录树,这样原始目录名就可以立即重用。这看起来确实有帮助。为此,我为rmtree
创建了一个替代品,其本质是:
def removetree(tgt):
def error_handler(func, path, execinfo):
e = execinfo[1]
if e.errno == errno.ENOENT or not os.path.exists(path):
return # path does not exist - treat as success
if func in (os.rmdir, os.remove) and e.errno == errno.EACCES:
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
func(path) # read-only file; make writable and retry
raise e
tmp = os.path.join(os.path.dirname(tgt),"_removetree_tmp")
os.rename(tgt, tmp)
shutil.rmtree(tmp, onerror=error_handler)
return
我发现这个逻辑是一个改进,但是它会受到os.rename
操作不可预知的失败的影响,其中一个可能会出现错误。因此,我还在os.rename
周围添加了一些重试逻辑,因此:
def removetree(tgt):
def error_handler(func, path, execinfo):
# figure out recovery based on error...
e = execinfo[1]
if e.errno == errno.ENOENT or not os.path.exists(path):
return # path does not exist
if func in (os.rmdir, os.remove) and e.errno == errno.EACCES:
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
func(path) # read-only file; make writable and retry
raise e
# Rename target directory to temporary value, then remove it
count = 0
while count < 10: # prevents indefinite loop
count += 1
tmp = os.path.join(os.path.dirname(tgt),"_removetree_tmp_%d"%(count))
try:
os.rename(tgt, tmp)
shutil.rmtree(tmp, onerror=error_handler)
break
except OSError as e:
time.sleep(1) # Give file system some time to catch up
if e.errno in [errno.EACCES, errno.ENOTEMPTY]:
continue # Try another temp name
if e.errno == errno.EEXIST:
shutil.rmtree(tmp, ignore_errors=True) # Try to clean up old files
continue # Try another temp name
if e.errno == errno.ENOENT:
break # 'src' does not exist(?)
raise # Other error - propagate
return
上面的代码没有经过测试,但是这里的一般想法似乎是有效的。我实际使用的完整代码如下,并使用两个函数。它可能包含了一些不必要的逻辑,但对我来说似乎更可靠(因为我的测试套件现在在Windows上反复通过,以前在大多数运行中它都无法预料地失败):
def renametree_temp(src):
"""
Rename tree to temporary name, and return that name, or
None if the source directory does not exist.
"""
count = 0
while count < 10: # prevents indefinite loop
count += 1
tmp = os.path.join(os.path.dirname(src),"_removetree_tmp_%d"%(count))
try:
os.rename(src, tmp)
return tmp # Success!
except OSError as e:
time.sleep(1)
if e.errno == errno.EACCES:
log.warning("util.renametree_temp: %s EACCES, retrying"%tmp)
continue # Try another temp name
if e.errno == errno.ENOTEMPTY:
log.warning("util.renametree_temp: %s ENOTEMPTY, retrying"%tmp)
continue # Try another temp name
if e.errno == errno.EEXIST:
log.warning("util.renametree_temp: %s EEXIST, retrying"%tmp)
shutil.rmtree(tmp, ignore_errors=True) # Try to clean up old files
continue # Try another temp name
if e.errno == errno.ENOENT:
log.warning("util.renametree_temp: %s ENOENT, skipping"%tmp)
break # 'src' does not exist(?)
raise # Other error: propagaee
return None
def removetree(tgt):
"""
Work-around for python problem with shutils tree remove functions on Windows.
See:
https://stackoverflow.com/questions/23924223/
https://stackoverflow.com/questions/1213706/
https://stackoverflow.com/questions/1889597/
http://bugs.python.org/issue19643
"""
# shutil.rmtree error handler that attempts recovery from attempts
# on Windows to remove a read-only file or directory (see links above).
def error_handler(func, path, execinfo):
e = execinfo[1]
if e.errno == errno.ENOENT or not os.path.exists(path):
return # path does not exist: nothing to do
if func in (os.rmdir, os.remove) and e.errno == errno.EACCES:
try:
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
except Exception as che:
log.warning("util.removetree: chmod failed: %s"%che)
try:
func(path)
except Exception as rfe:
log.warning("util.removetree: 'func' retry failed: %s"%rfe)
if not os.path.exists(path):
return # Gone, assume all is well
raise
if e.errno == errno.ENOTEMPTY:
log.warning("util.removetree: Not empty: %s, %s"%(path, tgt))
time.sleep(1)
removetree(path) # Retry complete removal
return
log.warning("util.removetree: rmtree path: %s, error: %s"%(path, repr(execinfo)))
raise e
# Try renaming to a new directory first, so that the tgt is immediately
# available for re-use.
tmp = renametree_temp(tgt)
if tmp:
shutil.rmtree(tmp, onerror=error_handler)
return
(上面的代码结合了在windows中运行python脚本的用户是什么?的只读文件问题的解决方案,根据删除Python中的目录对此进行了测试。我认为我没有遇到只读文件问题,所以假设它没有在我的测试套件中进行测试。)
https://stackoverflow.com/questions/23924223
复制相似问题