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


当前回答

下面是一个解决方案,它是标准库的一部分:

from distutils.dir_util import copy_tree
copy_tree("/a/b/c", "/x/y/z")

看这个类似的问题。

用python将目录内容复制到目录中

参考资料- https://docs.python.org/3/distutils/apiref.html#distutils.dir_util.copy_tree

其他回答

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

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

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

下面是一个需要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))。

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

受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对函数的回答略有改进,上面的函数总是尝试将文件从源复制到目标。

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