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


211

Запустите следующий код из каталога, который содержит каталог с именем 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
К вашему сведению: вот оригинальная функция copytree, просто скопируйте и
— исправьте

3
Существует проблемаshutil.copytree() с Python об изменении поведения, чтобы разрешить запись в существующий каталог, но есть некоторые детали поведения, с которыми необходимо согласовать.
— Ник Чаммас

2
Просто отметив, что упомянутый выше запрос на улучшение был реализован для Python 3.8: docs.python.org/3.8/whatsnew/3.8.html#shutil
— ncoghlan

Ответы:


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:

  • он не учитывает symlinksи ignoreпараметры для корневого каталога srcдерева;
  • это не вызывает shutil.Errorошибок на корневом уровне src;
  • в случае ошибок во время копирования поддерева, оно будет возбуждено shutil.Errorдля этого поддерева вместо того, чтобы пытаться копировать другие поддеревья и поднимать единое объединенное shutil.Error.

50
Спасибо! Согласитесь, что это кажется совершенно произвольным! shutil.copytreeделает os.makedirs(dst)в начале. Ни одна часть кода на самом деле не будет иметь проблемы с уже существующим каталогом. Это должно быть изменено. По крайней мере, предоставьте exist_ok=Falseпараметр для вызова
— cfi

6
Это хороший ответ - однако, ответ Митал Вора ниже также стоит посмотреть. Они вызвали copytree рекурсивно, а не вызывали shutil.copytree (), потому что в противном случае возникнет та же проблема. Возможно рассмотрите слияние ответов или обновление к Mital Vora's.
— PJeffes

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

8
Мех, супер раздражает. Это 4 года спустя, и у shutil.copytree все еще есть это глупое ограничение. :-(
— antred

5
@antred ... но distutils.dir_util.copy_tree(), который также находится в stdlib, не имеет такого ограничения и фактически ведет себя как ожидалось. Учитывая это, нет веских причин пытаться развернуть свою собственную ( ... обычно неработающую ) реализацию. Брендан Абель «s ответ должен абсолютно быть принятым решением в настоящее время.
— Сесил Карри

258

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

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

Смотрите этот похожий вопрос.

Скопируйте содержимое каталога в каталог с помощью Python


5
Это хорошо, потому что он использует стандартную библиотеку. Симлинки, режим и время также могут быть сохранены.
— его

1
Заметил небольшой недостаток. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a stringт.е. не принимает PosixPath. Надо str(PosixPath). Список пожеланий для улучшения. Помимо этого вопроса, я предпочитаю этот ответ.
— Солнечный Медведь

@ SunBear, да, я думаю, что это произойдет с большинством других библиотек, которые принимают пути в виде строк. Я полагаю, что есть и обратная сторона: не делать Pathобъект наследуемым str, как большинство предыдущих реализаций объектно-ориентированных объектов пути.
— Брендан Абель,

Кстати, я столкнулся с задокументированным недостатком этой функции. Это задокументировано здесь . Пользователям этой функции был совет, чтобы быть в курсе этого.
— Солнечный медведь

1
Хотя это «технически публично», обратите внимание, что разработчики distutils ясно дали понять (та же ссылка, что и у @ SunBear's, спасибо!), Что 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
Хорошо, за исключением того, что у вас есть символические ссылки и вы игнорируете их в качестве аргументов, но они игнорируются.
— Мэтью Альперт

Стоит отметить, что степень детализации st_mtime может быть грубой в течение 2 секунд в файловых системах FAT docs.python.org/2/library/os.html . Используя этот код в контексте быстрого обновления, вы можете обнаружить, что переопределения не выполняются.
— рБоп

Существует ошибка в последней строке, должно быть: 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 уже существует

Это намного быстрее, чем оригинальное решение, когда вложение каталогов глубокое. Спасибо
— Кашиф

Вы определили функцию, также называемую «игнорировать» в коде в другом месте?
— KenV99

Вы можете определить любую функцию с любым именем перед вызовом функции copytree. Эта функция (которая также может быть лямбда-выражением) принимает два аргумента: имя каталога и файлы в нем; она должна возвращать итерацию игнорируемых файлов.
— Сирил Понтвье,

[x for x in lst if x not in excl]это не делает то же самое, что copytree, который использует сопоставление с шаблоном glob. en.wikipedia.org/wiki/Glob_(programming)
— Константин Шуберт

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
@ Джей, только если каталог уже существует. Я dirs_exist_okупустил первый вызов, чтобы проиллюстрировать разницу (и потому что каталог еще не существует в примере OP), но, конечно, вы можете использовать его, если хотите.
— Крис

Спасибо, если вы добавите комментарий рядом с первой копией, я думаю, что это сделает это более ясным :)
— Джей

7

Документы прямо указывают, что каталог назначения не должен существовать :

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

Я думаю, что ваш лучший выбор для os.walkвторого и всех последующих каталогов, copy2каталогов и файлов и делать дополнительные copystatдля каталогов. В конце концов, это именно то copytree, что описано в документации. Или вы можете copyи copystatкаждый каталог / файл и 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)

Раскомментируйте дерево, чтобы оно стало функцией перемещения.


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.

updateonlyis 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

0

Предыдущее решение имеет проблему, которая srcможет быть перезаписана dstбез какого-либо уведомления или исключения.

Я добавляю predict_errorметод для прогнозирования ошибок перед копированием. copytreeв основном на основе версии Сирила Понтье.

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)

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

й?


если вы работаете в Windows ... скачайте 7zip .. и используйте для этого командную строку. ... опять просто предложения.
— Кирби

31
Системные команды всегда должны быть последним средством. Всегда лучше по возможности использовать стандартную библиотеку, чтобы ваш код был переносимым.
— Ятанизм
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.