Как скопировать весь каталог файлов в существующий каталог с помощью Python?

Запустите следующий код из каталога, который содержит каталог с именем 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? (После того, как я уже скопировал содержимое "bar" в "foo" с помощью shutil.copytree()?) Или есть более простой/лучший способ?

Ответ 1

Это ограничение стандарта 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 и ignore для корневого каталога дерева src;
  • он не поднимает shutil.Error для ошибок на уровне корня src;
  • в случае ошибок при копировании поддерева, он поднимет shutil.Error для этого поддерева вместо того, чтобы пытаться скопировать другие поддеревья и собрать один комбинированный shutil.Error.

Ответ 3

В небольшом улучшении 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

Слияние одного, вдохновленного 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 уже существует

Ответ 5

docs явно заявляют, что целевой каталог должен не:

Целевой каталог, названный dst, уже не должен существовать; он будет создан, а также отсутствующие родительские каталоги.

Думаю, ваш лучший выбор - os.walk второй и все последующие каталоги, copy2 каталог и файлы и сделать дополнительные copystat для каталогов. В конце концов, именно то, что copytree делает, как объяснено в документах. Или вы могли copy и copystat каждый каталог/файл и os.listdir вместо os.walk.

Ответ 6

Вы можете модифицировать shutil и получить эффект (в моей версии shutil это строка 315)

+ Изменить

os.makedirs(dst)

к

os.makedirs(dst,exist_ok=True)

Ответ 7

Это вдохновлено оригинальным лучшим ответом, предоставленным 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, чтобы сделать его функцией перемещения.

Ответ 8

Я предполагаю, что самый быстрый и простой способ - заставить python вызывать системные команды...

пример..

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

Тар и gzip вверх по директории.... разархивируйте и распакуйте каталог в нужном месте.

й?

Ответ 9

Вот моя версия той же задачи::

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

Вот версия, вдохновленная этой нитью, которая более близка имитирует distutils.file_util.copy_file.

updateonly - это bool, если True, будет копировать только файлы с измененными датами, более новыми, чем существующие файлы в dst, если они не указаны в forceupdate, которые будут скопированы независимо.

ignore и forceupdate ожидают списки имен файлов или файлов/файлов по отношению к src и принимают подстановочные знаки Unix-стиля, похожие на glob или 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

Ответ 11

Предыдущее решение имеет некоторую проблему: 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

Вот мой вопрос по проблеме. Я изменил исходный код для копирования, чтобы сохранить исходную функциональность, но теперь ошибка не возникает, когда каталог уже существует. Я также изменил его, чтобы он не перезаписывал существующие файлы, а сохранял обе копии с измененным именем, поскольку это было важно для моего приложения.

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

Попробуй это:

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

Вот версия, которая ожидает pathlib.Path в качестве ввода.

# Recusively copies the content of the directory src to the directory dst.
# If dst does not 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)).