Как скопировать весь каталог файлов в существующий каталог с помощью 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()?) Или есть более простой/лучший способ?

12 ответов


это ограничение стандарта 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.

вот решение, которое является частью стандартной библиотеки.

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

посмотреть этот же вопрос.

скопируйте содержимое каталога в каталог с python


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


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

в документах явно указано, что каталог назначения должен не exist:

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

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


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

изменить

os.makedirs(dst)

до

os.makedirs(dst,exist_ok=True)

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

пример..

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

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

да ну?


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


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

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)

вот версия, вдохновленная этой нитью, которая более точно имитирует 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

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

вот мой пропуск в проблему. Я изменил исходный код для 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)