如何使用Python将整个文件目录复制到现有目录中?

Dar*_*zer 182 python shutil copytree

从包含名为bar(包含一个或多个文件)的目录和名为baz(还包含一个或多个文件)的目录的目录中运行以下代码.确保没有名为的目录foo.

import shutil
shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo')
Run Code Online (Sandbox Code Playgroud)

它将失败:

$ 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'
Run Code Online (Sandbox Code Playgroud)

我希望它的工作方式与我输入的方式相同:

$ mkdir foo
$ cp bar/* foo/
$ cp baz/* foo/
Run Code Online (Sandbox Code Playgroud)

我是否需要使用shutil.copy()将每个文件复制bazfoo?(之后我已经将'bar'的内容复制到'foo'中shutil.copytree()?)或者是否有更简单/更好的方法?

Bre*_*bel 214

这是一个标准库的解决方案.

from distutils.dir_util import copy_tree
copy_tree("/a/b/c", "/x/y/z")
Run Code Online (Sandbox Code Playgroud)

看到这个类似的问题.

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

  • **distutils 已在 Python 3.10 中弃用**,并计划在 3.12 中删除,请参阅 [PEP 632](https://www.python.org/dev/peps/pep-0632/) 了解信息。 (37认同)
  • 这是一个很好的,因为它使用标准库.符号链接,模式和时间也可以保留. (5认同)
  • 注意到一个小缺点。`distutils.errors.DistutilsInternalError: mkpath: 'name' 必须是一个字符串`,即它不接受 `PosixPath`。需要`str(PosixPath)`。改进愿望清单。除了这个问题,我更喜欢这个答案。 (4认同)
  • 虽然“技术上公开”,但请注意 distutils 的开发人员[明确表示](https://bugs.python.org/issue10948)(与 @SunBear 的链接相同,谢谢!)`distutils.dir_util.copy_tree() ` 被认为是 distutils 的实现细节,不建议公众使用。真正的解决方案应该是改进/扩展“shutil.copytree()”,使其表现得更像“distutils.dir_util.copy_tree()”,但没有其缺点。与此同时,我将继续使用与其他答案中提供的一些类似的自定义辅助函数。 (3认同)

atz*_*tzz 153

标准的这种限制shutil.copytree似乎是任意和令人讨厌的.解决方法:

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)
Run Code Online (Sandbox Code Playgroud)

请注意,它与标准copytree不完全一致:

  • 它没有表示树的根目录symlinksignore参数src;
  • 它不会引起shutil.Error根级别的错误src;
  • 如果在复制子树期间出现错误,它将shutil.Error针对该子树而不是尝试复制其他子树并提高单个组合shutil.Error.

  • 谢谢!同意这看起来完全是武断的!`shutil.copytree`在开始时执行`os.makedirs(dst)`.代码的任何部分实际上都不会存在预先存在的dir的问题.这需要改变.至少为调用提供一个`exist_ok = False`参数 (45认同)
  • 嗯,超级烦人.4年后,shutil.copytree仍然有这种愚蠢的限制.:-( (8认同)
  • 这是一个很好的答案 - 但是下面的Mital Vora的答案值得一看.他们以递归方式调用copytree而不是调用shutil.copytree(),否则会出现同样的问题.可能会考虑合并答案或更新到Mital Vora. (6认同)
  • 如果给定的路径包含目标中不为空的目录,则会失败.也许有人可以通过尾递归来解决这个问题,但是这里修改了你的代码`def copyTree(src,dst,symlinks = False,ignore = None):对于os.listdir(src)中的项:s = os.path. join(src,item)d = os.path.join(dst,item)if os.path.isdir(s):if os.path.isdir(d):self.recursiveCopyTree(s,d,symlinks,ignore) else:shutil.copytree(s,d,symlinks,ignore)else:shutil.copy2(s,d)` (4认同)
  • @antred ...但是`*distutils.dir_util.copy_tree()`,*也*驻留在stdlib中,没有这样的限制,实际上表现得如预期的那样.鉴于此,没有令人信服的理由尝试展开您自己的(*...通常已损坏的*)实现.[Brendan Abel](/sf/users/108290311/)的[答案](/sf/answers/2172736681/)现在绝对应该是公认的解决方案. (4认同)
  • 几年后,shutil 解决了这个问题!对于所有 python &gt;= 3.8,有一个 `dirs_exist_ok` 可选参数 (3认同)

Chr*_*ris 62

Python的3.8引入了dirs_exist_ok论证shutil.copytree

递归复制以src为根的整个目录树到名为dst的目录并返回目标目录。dirs_exist_ok指示是否在dst或任何丢失的父目录已经存在的情况下引发异常。

因此,使用 Python 3.8+ 这应该可以工作:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)
Run Code Online (Sandbox Code Playgroud)

  • @Jay,仅当目录已存在时。我在第一次调用中保留了“dirs_exist_ok”以说明差异(并且因为该目录在OP的示例中尚不存在),但是当然,如​​果您愿意,您可以使用它。 (2认同)
  • @pcko1,假设您的意思是`foo/bar/`而不是`bar/foo/`,请尝试`shutil.copytree("bar", "foo/bar", dirs_exist_ok=True)`。如果您不想硬编码“/”目录分隔符,“pathlib.Path”对象也可以工作。 (2认同)

Mit*_*ora 55

在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)
Run Code Online (Sandbox Code Playgroud)

在我上面的实现中

  • 如果尚不存在,则创建输出目录
  • 通过递归调用我自己的方法来执行复制目录.
  • 当我们实际复制文件时,我检查文件是否被修改,然后我们只应该复制.

我正在使用上面的函数和scons构建.它对我帮助很大,因为每次编译时我都可能不需要复制整套文件......但只需要修改的文件.

  • 很好,除了你有符号链接和忽略作为参数,但它们被忽略. (4认同)

Cyr*_*eux 32

由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)
Run Code Online (Sandbox Code Playgroud)
  • shutil.copytree相同的行为,包含符号链接忽略参数
  • 如果不存在,则创建目录目标结构
  • 如果dst已经存在,则不会失败

  • 这很棒.在上面的答案中没有正确使用忽略. (2认同)

Sil*_*ost 7

文件明确指出目标目录应该存在:

名为的目标目录dst必须不存在; 它将被创建以及缺少父目录.

我认为你最好的选择是os.walk第二个和所有后续目录,copy2目录和文件,并copystat为目录做额外的事情.毕竟,copytree正如文档中所解释的那样.或者你可以copycopystat每个目录/文件而os.listdir不是os.walk.