Как мне скопировать весь каталог файлов в существующий каталог, используя 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.