在一个目录中运行以下代码,该目录包含一个名为bar的目录(包含一个或多个文件)和一个名为baz的目录(也包含一个或多个文件)。确保没有名为foo的目录。
import shutil
shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo')
它将失败:
$ python copytree_test.py
Traceback (most recent call last):
File "copytree_test.py", line 5, in <module>
shutil.copytree('baz', 'foo')
File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/shutil.py", line 110, in copytree
File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/os.py", line 172, in makedirs
OSError: [Errno 17] File exists: 'foo'
我想让它像我输入的那样工作:
$ mkdir foo
$ cp bar/* foo/
$ cp baz/* foo/
我需要使用shutil.copy()复制每个文件在baz到foo?(在我已经用shutil.copytree()将'bar'的内容复制到'foo'后?)或者有更简单/更好的方法吗?
这是标准书板的局限性。Copytree似乎很随意,很烦人。处理:
import os, shutil
def copytree(src, dst, symlinks=False, ignore=None):
for item in os.listdir(src):
s = os.path.join(src, item)
d = os.path.join(dst, item)
if os.path.isdir(s):
shutil.copytree(s, d, symlinks, ignore)
else:
shutil.copy2(s, d)
注意,它与标准复制树并不完全一致:
它不尊重符号链接,忽略SRC树根目录的参数;
它不提高shutil。src根级别的错误;
如果在复制子树期间发生错误,它将引发shutil。错误,而不是试图复制其他子树并引发单个组合shutil.Error。
在atzz对函数的回答略有改进,上面的函数总是尝试将文件从源复制到目标。
def copytree(src, dst, symlinks=False, ignore=None):
if not os.path.exists(dst):
os.makedirs(dst)
for item in os.listdir(src):
s = os.path.join(src, item)
d = os.path.join(dst, item)
if os.path.isdir(s):
copytree(s, d, symlinks, ignore)
else:
if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
shutil.copy2(s, d)
在我上面的实现中
如果不存在,则创建输出目录
通过递归调用我自己的方法来复制目录。
当我们实际复制文件时,我检查文件是否被修改了
我们应该效仿。
我使用上面的函数以及scons构建。它帮助了我很多,因为每次当我编译时,我可能不需要复制整个文件集。但仅限于修改过的文件。
受atzz和Mital Vora启发的合并:
#!/usr/bin/python
import os
import shutil
import stat
def copytree(src, dst, symlinks = False, ignore = None):
if not os.path.exists(dst):
os.makedirs(dst)
shutil.copystat(src, dst)
lst = os.listdir(src)
if ignore:
excl = ignore(src, lst)
lst = [x for x in lst if x not in excl]
for item in lst:
s = os.path.join(src, item)
d = os.path.join(dst, item)
if symlinks and os.path.islink(s):
if os.path.lexists(d):
os.remove(d)
os.symlink(os.readlink(s), d)
try:
st = os.lstat(s)
mode = stat.S_IMODE(st.st_mode)
os.lchmod(d, mode)
except:
pass # lchmod not available
elif os.path.isdir(s):
copytree(s, d, symlinks, ignore)
else:
shutil.copy2(s, d)
与shutil的行为相同。复制树,具有符号链接和忽略参数
如果不存在,创建目录目标结构
如果dst已经存在,将不会失败
这是受到atzz提供的原始最佳答案的启发,我只是添加了替换文件/文件夹逻辑。所以它实际上并没有合并,而是删除现有的文件/文件夹并复制新的文件/文件夹:
import shutil
import os
def copytree(src, dst, symlinks=False, ignore=None):
for item in os.listdir(src):
s = os.path.join(src, item)
d = os.path.join(dst, item)
if os.path.exists(d):
try:
shutil.rmtree(d)
except Exception as e:
print e
os.unlink(d)
if os.path.isdir(s):
shutil.copytree(s, d, symlinks, ignore)
else:
shutil.copy2(s, d)
#shutil.rmtree(src)
取消注释rmtree,使其成为一个move函数。
下面是这个线程启发的一个版本,它更接近地模仿distutils.file_util.copy_file。
updateonly是bool值,如果为True,将只复制修改日期比dst中现有文件更新的文件,除非在forceupdate中列出,否则无论如何都会复制。
ignore和forceupdate expect相对于src的文件名或文件夹/文件名列表,并接受类似于glob或fnmatch的unix风格通配符。
该函数返回复制的文件列表(或者如果dryrun为True,将被复制)。
import os
import shutil
import fnmatch
import stat
import itertools
def copyToDir(src, dst, updateonly=True, symlinks=True, ignore=None, forceupdate=None, dryrun=False):
def copySymLink(srclink, destlink):
if os.path.lexists(destlink):
os.remove(destlink)
os.symlink(os.readlink(srclink), destlink)
try:
st = os.lstat(srclink)
mode = stat.S_IMODE(st.st_mode)
os.lchmod(destlink, mode)
except OSError:
pass # lchmod not available
fc = []
if not os.path.exists(dst) and not dryrun:
os.makedirs(dst)
shutil.copystat(src, dst)
if ignore is not None:
ignorepatterns = [os.path.join(src, *x.split('/')) for x in ignore]
else:
ignorepatterns = []
if forceupdate is not None:
forceupdatepatterns = [os.path.join(src, *x.split('/')) for x in forceupdate]
else:
forceupdatepatterns = []
srclen = len(src)
for root, dirs, files in os.walk(src):
fullsrcfiles = [os.path.join(root, x) for x in files]
t = root[srclen+1:]
dstroot = os.path.join(dst, t)
fulldstfiles = [os.path.join(dstroot, x) for x in files]
excludefiles = list(itertools.chain.from_iterable([fnmatch.filter(fullsrcfiles, pattern) for pattern in ignorepatterns]))
forceupdatefiles = list(itertools.chain.from_iterable([fnmatch.filter(fullsrcfiles, pattern) for pattern in forceupdatepatterns]))
for directory in dirs:
fullsrcdir = os.path.join(src, directory)
fulldstdir = os.path.join(dstroot, directory)
if os.path.islink(fullsrcdir):
if symlinks and dryrun is False:
copySymLink(fullsrcdir, fulldstdir)
else:
if not os.path.exists(directory) and dryrun is False:
os.makedirs(os.path.join(dst, dir))
shutil.copystat(src, dst)
for s,d in zip(fullsrcfiles, fulldstfiles):
if s not in excludefiles:
if updateonly:
go = False
if os.path.isfile(d):
srcdate = os.stat(s).st_mtime
dstdate = os.stat(d).st_mtime
if srcdate > dstdate:
go = True
else:
go = True
if s in forceupdatefiles:
go = True
if go is True:
fc.append(d)
if not dryrun:
if os.path.islink(s) and symlinks is True:
copySymLink(s, d)
else:
shutil.copy2(s, d)
else:
fc.append(d)
if not dryrun:
if os.path.islink(s) and symlinks is True:
copySymLink(s, d)
else:
shutil.copy2(s, d)
return fc
以前的解决方案有一些问题,src可能会覆盖dst没有任何通知或异常。
我添加了一个predict_error方法来在复制之前预测错误。复制树主要基于西里尔庞特维欧的版本。
首先使用predict_error预测所有错误是最好的,除非您希望在执行copytree直至修复所有错误时看到一个接一个地引发异常。
def predict_error(src, dst):
if os.path.exists(dst):
src_isdir = os.path.isdir(src)
dst_isdir = os.path.isdir(dst)
if src_isdir and dst_isdir:
pass
elif src_isdir and not dst_isdir:
yield {dst:'src is dir but dst is file.'}
elif not src_isdir and dst_isdir:
yield {dst:'src is file but dst is dir.'}
else:
yield {dst:'already exists a file with same name in dst'}
if os.path.isdir(src):
for item in os.listdir(src):
s = os.path.join(src, item)
d = os.path.join(dst, item)
for e in predict_error(s, d):
yield e
def copytree(src, dst, symlinks=False, ignore=None, overwrite=False):
'''
would overwrite if src and dst are both file
but would not use folder overwrite file, or viceverse
'''
if not overwrite:
errors = list(predict_error(src, dst))
if errors:
raise Exception('copy would overwrite some file, error detail:%s' % errors)
if not os.path.exists(dst):
os.makedirs(dst)
shutil.copystat(src, dst)
lst = os.listdir(src)
if ignore:
excl = ignore(src, lst)
lst = [x for x in lst if x not in excl]
for item in lst:
s = os.path.join(src, item)
d = os.path.join(dst, item)
if symlinks and os.path.islink(s):
if os.path.lexists(d):
os.remove(d)
os.symlink(os.readlink(s), d)
try:
st = os.lstat(s)
mode = stat.S_IMODE(st.st_mode)
os.lchmod(d, mode)
except:
pass # lchmod not available
elif os.path.isdir(s):
copytree(s, d, symlinks, ignore)
else:
if not overwrite:
if os.path.exists(d):
continue
shutil.copy2(s, d)
这是我对这个问题的看法。我修改了copytree的源代码以保持原来的功能,但是现在当目录已经存在时不会发生错误。我还修改了它,这样它就不会覆盖现有的文件,而是保留两个副本,其中一个具有修改后的名称,因为这对我的应用程序很重要。
import shutil
import os
def _copytree(src, dst, symlinks=False, ignore=None):
"""
This is an improved version of shutil.copytree which allows writing to
existing folders and does not overwrite existing files but instead appends
a ~1 to the file name and adds it to the destination path.
"""
names = os.listdir(src)
if ignore is not None:
ignored_names = ignore(src, names)
else:
ignored_names = set()
if not os.path.exists(dst):
os.makedirs(dst)
shutil.copystat(src, dst)
errors = []
for name in names:
if name in ignored_names:
continue
srcname = os.path.join(src, name)
dstname = os.path.join(dst, name)
i = 1
while os.path.exists(dstname) and not os.path.isdir(dstname):
parts = name.split('.')
file_name = ''
file_extension = parts[-1]
# make a new file name inserting ~1 between name and extension
for j in range(len(parts)-1):
file_name += parts[j]
if j < len(parts)-2:
file_name += '.'
suffix = file_name + '~' + str(i) + '.' + file_extension
dstname = os.path.join(dst, suffix)
i+=1
try:
if symlinks and os.path.islink(srcname):
linkto = os.readlink(srcname)
os.symlink(linkto, dstname)
elif os.path.isdir(srcname):
_copytree(srcname, dstname, symlinks, ignore)
else:
shutil.copy2(srcname, dstname)
except (IOError, os.error) as why:
errors.append((srcname, dstname, str(why)))
# catch the Error from the recursive copytree so that we can
# continue with other files
except BaseException as err:
errors.extend(err.args[0])
try:
shutil.copystat(src, dst)
except WindowsError:
# can't copy file access times on Windows
pass
except OSError as why:
errors.extend((src, dst, str(why)))
if errors:
raise BaseException(errors)
下面是一个需要pathlib的版本。路径作为输入。
# Recusively copies the content of the directory src to the directory dst.
# If dst doesn't exist, it is created, together with all missing parent directories.
# If a file from src already exists in dst, the file in dst is overwritten.
# Files already existing in dst which don't exist in src are preserved.
# Symlinks inside src are copied as symlinks, they are not resolved before copying.
#
def copy_dir(src, dst):
dst.mkdir(parents=True, exist_ok=True)
for item in os.listdir(src):
s = src / item
d = dst / item
if s.is_dir():
copy_dir(s, d)
else:
shutil.copy2(str(s), str(d))
注意,这个函数需要Python 3.6,这是Python的第一个版本,其中os.listdir()支持类似路径的对象作为输入。如果需要支持早期版本的Python,可以将listdir(str(src))替换为listdir(str(src))。
我无法编辑上面的“Boris Dalstein”答案,所以这里是这段代码的改进版本:
编辑所做的改进:
输入参数可以是str path或pathlib。路径对象。输入提示会有帮助。
如果源是一个目录,它也会创建该目录
类型是为局部变量定义的,因此IDE不会发出警告
# Recusively copies the content of the directory src to the directory dst.
# If dst doesn't exist, it is created, together with all missing parent directories.
# If a file from src already exists in dst, the file in dst is overwritten.
# Files already existing in dst which don't exist in src are preserved.
# Symlinks inside src are copied as symlinks, they are not resolved before copying.
#
def copy_dir(source: Union[str, pathlib.Path], destination: Union[str, pathlib.Path]):
destination_path: pathlib.Path
if isinstance(source, str):
source_path = pathlib.Path(source)
elif isinstance(source, pathlib.Path):
source_path = source
if isinstance(destination, str):
destination_path = pathlib.Path(destination)
elif isinstance(destination, pathlib.Path):
destination_path = destination
destination_path.mkdir(parents=True, exist_ok=True)
if source_path.is_dir():
destination_path = destination_path.joinpath(source_path.name)
destination_path.mkdir(parents=True, exist_ok=True)
for item in os.listdir(source_path):
s: pathlib.Path = source_path / item
d: pathlib.Path = destination_path / item
if s.is_dir():
copy_dir(s, d)
else:
shutil.copy2(str(s), str(d))