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

时间:2009-12-08 17:56:05

标签: python shutil copytree

从包含名为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'后?)或者有更简单/更好的方法吗?

15 个答案:

答案 0 :(得分:157)

这是一个解决方案,它是标准库的一部分。

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

看到这个类似的问题。

Copy directory contents into a directory with python

答案 1 :(得分:138)

标准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)

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

  • 它不尊重symlinks树的根目录的ignoresrc参数;
  • 它不会在shutil.Error;
  • 的根级错误引发src
  • 如果在复制子树时出现错误,则会为该子树引发shutil.Error,而不是尝试复制其他子树并提升单个组合shutil.Error

答案 2 :(得分:51)

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构建。每次编译时它都帮助了我很多,我可能不需要复制整套文件..但只需要修改文件。

答案 3 :(得分:30)

受到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 已经存在,则不会失败

答案 4 :(得分:7)

docs explicitly state that destination directory should not exist

  

目标目录(由dst命名)必须不存在;它将被创建以及缺少父目录。

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

答案 5 :(得分:4)

Python 3.8 introduced the dirs_exist_ok argumentshutil.copytree

  

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

因此,在Python 3.8+中,这应该可以工作:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)

答案 6 :(得分:2)

您可以修改shutil并获得效果(我的shutil版本就在315上)

更改

os.makedirs(dst)

os.makedirs(dst,exist_ok=True)

答案 7 :(得分:1)

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

例如..

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

tar并gzip up the directory ....解压缩并解压缩所需位置的目录。

呀?

答案 8 :(得分: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以使其成为移动功能。

答案 9 :(得分: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)

答案 10 :(得分:0)

Here is a version inspired by this thread that more closely mimics is.

distutils.file_util.copy_file is a bool if True, will only copy files with modified dates newer than existing files in updateonly unless listed in dst which will copy regardless.

forceupdate and ignore expect lists of filenames or folder/filenames relative to forceupdate and accept Unix-style wildcards similar to src or glob.

The function returns a list of files copied (or would be copied if fnmatch if True).

dryrun

答案 11 :(得分: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)

答案 12 :(得分: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)

答案 13 :(得分: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")

答案 14 :(得分: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))