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


210

从包含一个名为目录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'之后)还是有更简单/更好的方法?


1
仅供参考:是原始的copytree函数,只需对其进行复制和修补即可:)
schlamar 2012年

3
有一个关于change shutil.copytree()的行为Python问题,以允许写入现有目录,但是有一些行为细节需要达成共识。
Nick Chammas 2015年

2
只是注意到上述增强请求已针对Python 3.8实现:docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan

Answers:


174

标准的这种限制shutil.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)

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

  • 它不接受symlinksignore对根目录下的参数src树;
  • 它不会从根本上引发shutil.Error错误src;
  • 如果在复制子树期间出错,它将shutil.Error为该子树引发,而不是尝试复制其他子树并引发单个组合shutil.Error

50
谢谢!同意这似乎完全是任意的!shutil.copytreeos.makedirs(dst)开始就做一个。实际上,代码的任何部分都不会与预先存在的目录有关。这需要更改。至少提供一个exist_ok=False参数来调用
CFI

6
这是一个很好的答案-但是下面的Mital Vora的答案也值得一看。他们已经递归调用了copytree而不是调用shutil.copytree(),因为否则会出现相同的问题。可能考虑合并答案或更新Mital Vora的答案。
PJeffes 2013年

4
如果给定的路径包含目标中不为空的目录,则此操作将失败。也许有人可以通过尾递归来解决这个问题,但这是对您的代码的有效修改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): if os.path.isdir(d): self.recursiveCopyTree(s, d, symlinks, ignore) else: shutil.copytree(s, d, symlinks, ignore) else: shutil.copy2(s, d)
Sojurn 2015年

8
嗯,超级讨厌。4年后,shutil.copytree仍然有这个愚蠢的限制。:-(
antred

5
@antred ... but distutils.dir_util.copy_tree(),它驻留在stdlib中,没有这种限制并且实际上表现出预期的效果。鉴于此,没有令人信服的理由尝试展开自己的(通常是损坏的)实现。布伦丹·阿贝尔Brendan Abel)的答案现在绝对应该是公认的解决方案。
塞西尔·库里

257

这是标准库的一部分的解决方案:

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

看到这个类似的问题。

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


5
这是一个很好的选择,因为它使用了标准库。符号链接,模式和时间也可以保留。
itsafire

1
注意到一个小的缺点。distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string即不接受PosixPath。需要str(PosixPath)。希望得到改善。除了这个问题,我更喜欢这个答案。
太阳熊

@SunBear,是的,我认为其他大多数将路径作为字符串的库都是这种情况。下行的一部分选择不使Path对象继承str我想,最喜欢的面向对象路径对象..事先实现
布伦丹·阿贝尔

顺便说一句,我遇到了此功能的记录不足。它记录在这里。建议使用此功能的用户注意这一点。
太阳熊

1
尽管是“技术上公开的”,但请注意distutils的开发者明确表示(与@SunBear的链接相同,thx!),这distutils.dir_util.copy_tree()被视为distutils的实现细节,不建议公众使用。真正的解决方案应该是shutil.copytree()进行改进/扩展,使其行为更像distutils.dir_util.copy_tree(),但没有缺点。同时,我将继续使用类似于其他答案中提供的一些自定义帮助程序功能。
鲍里斯·达尔斯泰因

61

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构建。每次编译时,我可能不需要复制整个文件集,而只复制了已修改的文件,这对我很有帮助。


4
很好,除了您有符号链接并忽略它们作为参数,但它们被忽略。
马修·阿尔珀特

值得注意的是,在FAT文件系统docs.python.org/2/library/os.html上,st_mtime粒度可以粗略至2秒。在快速连续进行更新的环境中使用此代码,您可能会发现覆盖不会发生。
dgh 2014年

倒数第二行中存在错误,应为: if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec

34

受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.copytree相同的行为,带有符号链接忽略参数
  • 创建目录目标结构(如果不存在)
  • 如果dst已经存在,将不会失败

当目录嵌套很深时,这比原始解决方案快得多。谢谢
Kashif

您是否在其他代码中定义了一个也称为“忽略”的函数?
KenV99 '02

您可以在调用copytree函数之前,使用任意名称定义任何函数。此函数(也可以是lambda表达式)带有两个参数:目录名和其中的文件,它应返回一个可迭代的ignore文件。
Cyrille Pontvieux'2

[x for x in lst if x not in excl]这与使用全局模式匹配的copytree不同。zh.wikipedia.org/wiki/Glob_(编程)
康斯坦丁·舒伯特

2
这很棒。在上面的答案中忽略未被正确利用。
基思·霍利迪

21

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)

dirs_exist_ok=False默认情况下在copytree中,第一次复制尝试不会失败吗?
周杰伦

1
@Jay,仅当目录已存在时。我省略dirs_exist_ok了第一个调用来说明差异(并且因为目录在OP的示例中尚不存在),但是当然可以根据需要使用它。
克里斯

谢谢,如果您在第一个副本附近添加评论,我想它会使内容更清晰:)
周杰伦

7

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

以命名的目标目录dst必须不存在;它会被创建以及丢失的父目录。

我认为您最好的选择是os.walk第二个及所有后续目录,copy2目录和文件,copystat并对目录进行更多处理。毕竟,这正是copytree文档中所解释的。或者你可以copycopystat每个目录/文件os.listdir来代替os.walk


1

这是受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使其具有移动功能。


0

这是同一任务的我的版本:

import os, glob, shutil

def make_dir(path):
    if not os.path.isdir(path):
        os.mkdir(path)


def copy_dir(source_item, destination_item):
    if os.path.isdir(source_item):
        make_dir(destination_item)
        sub_items = glob.glob(source_item + '/*')
        for sub_item in sub_items:
            copy_dir(sub_item, destination_item + '/' + sub_item.split('/')[-1])
    else:
        shutil.copy(source_item, destination_item)

0

这是受此线程启发更紧密地模仿的版本distutils.file_util.copy_file

updateonly如果为True,则为bool,将仅复制修改日期比现有文件新的修改日期的文件,dst除非列出的forceupdate内容无论如何都会复制。

ignoreforceupdate期望相对于 文件名或文件夹/文件名的列表,src并接受类似于glob或的Unix样式的通配符fnmatch

该函数返回已复制文件的列表(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

0

先前的解决方案存在一些问题,这些问题src可能会在dst没有任何通知或异常的情况下被覆盖。

我添加了predict_error一种在复制之前预测错误的方法。copytree主要基于Cyrille Pontvieux的版本。

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)

0

这是我通过的问题。我修改了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)

0

试试这个:

import os,shutil

def copydir(src, dst):
  h = os.getcwd()
  src = r"{}".format(src)
  if not os.path.isdir(dst):
     print("\n[!] No Such directory: ["+dst+"] !!!")
     exit(1)

  if not os.path.isdir(src):
     print("\n[!] No Such directory: ["+src+"] !!!")
     exit(1)
  if "\\" in src:
     c = "\\"
     tsrc = src.split("\\")[-1:][0]
  else:
    c = "/"
    tsrc = src.split("/")[-1:][0]

  os.chdir(dst)
  if os.path.isdir(tsrc):
    print("\n[!] The Directory Is already exists !!!")
    exit(1)
  try:
    os.mkdir(tsrc)
  except WindowsError:
    print("\n[!] Error: In[ {} ]\nPlease Check Your Dirctory Path !!!".format(src))
    exit(1)
  os.chdir(h)
  files = []
  for i in os.listdir(src):
    files.append(src+c+i)
  if len(files) > 0:
    for i in files:
        if not os.path.isdir(i):
            shutil.copy2(i, dst+c+tsrc)

  print("\n[*] Done ! :)")

copydir("c:\folder1", "c:\folder2")

0

这是一个期望pathlib.Path输入的版本。

# 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(src)listdir(str(src))


-2

我认为最快最简单的方法是让python调用系统命令...

例..

import os
cmd = '<command line call>'
os.system(cmd)

将目录压缩并压缩。...将目录解压缩并解压缩到所需位置。

是啊


如果您在Windows中运行...下载7zip ..并使用命令行。...再一次只是建议。
柯比

31
系统命令应始终是万不得已的方法。最好尽可能使用标准库,以便代码可移植。
詹森主义2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.