在一个目录中运行以下代码,该目录包含一个名为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'后?)或者有更简单/更好的方法吗?


当前回答

这是受到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函数。

其他回答

以前的解决方案有一些问题,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)

在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构建。它帮助了我很多,因为每次当我编译时,我可能不需要复制整个文件集。但仅限于修改过的文件。

Docs明确指出目标目录不应该存在:

以dst命名的目标目录必须不存在;它将被创建,同时还将创建缺失的父目录。

我觉得你最好的选择是。遍历第二个和所有后续目录、copy2目录和文件,并对目录执行额外的copyystat。毕竟,这正是copytree在文档中所做的解释。或者你可以复制和复制每个目录/文件和操作系统。而不是os.walk。

这是标准书板的局限性。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。