Як скопіювати весь каталог файлів у існуючий каталог за допомогою Python?


210

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

3
Існує проблема Python щодо зміни shutil.copytree()поведінки, щоб дозволити записувати в існуючий каталог, але є деякі деталі поведінки, про які потрібно узгодити.
Нік Шамма

Відповіді:


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)на початку. Жодна частина коду насправді не мала б проблеми з існуючим dir. Це потрібно змінити. Принаймні надайте exist_ok=Falseпараметр виклику
cfi

6
Це хороша відповідь - однак відповідь Мітала Вора нижче варто також переглянути. Вони назвали copytree рекурсивно, а не викликати shutil.copytree (), тому що та сама проблема виникне інакше. Можливо, подумайте про об'єднання відповідей або оновлення до Мітала Вори.
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 відповідь повинен абсолютно бути прийнятим рішенням в даний час.
Сесіль Карі

257

Ось рішення, яке є частиною стандартної бібліотеки:

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

Дивіться подібне запитання.

Скопіюйте вміст каталогів у каталог із python


5
Це добре, оскільки він використовує стандартну бібліотеку. Також можуть бути збережені символьні посилання, режим та час.
itsafire

1
Помітив невеликий недолік. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string, тобто не приймає PosixPath. Потрібно str(PosixPath). Список побажань для вдосконалення. Крім цього питання, я віддаю перевагу цій відповіді.
Ведмідь

@SunBear, Так, я думаю, що це станеться з більшості інших бібліотек, які сприймають контури як рядки. Я думаю, що частина недоліків у виборі не робити Pathоб’єкт успадкованим str, як і більшість попередніх реалізацій об'єктно-орієнтованих шляхів ..
Брендан Абель

До речі, я зіткнувся з документально підтвердженим недоліком цієї функції. Це задокументовано тут . Користувачам цієї функції були поради, які слід знати про неї.
Ведмідь Сонця

1
Незважаючи на те, що "з технічної точки зору", зауважте, що розробники distutils дали зрозуміти (те саме посилання, як @ SunBear's, thx!), Що 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
Приємно, за винятком того, що у вас є символьні посилання та ігноруються як аргументи, але вони ігноруються.
Меттью Алперт

Варто зазначити, що в файлових системах FAT docs.python.org/2/library/os.html деталізація st_mtime може бути такою ж грубою, як і 2 секунди . Використовуючи цей код у контексті, коли оновлення відбуваються швидко, можна виявити, що переопрацювання не відбувається.
dgh

У другому до останнього рядка є помилка, має бути: 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 вже існує

Це набагато швидше, ніж оригінальне рішення, коли каталог вкладений глибоко. Спасибі
Кашиф

Ви визначили функцію, яка також називається 'ignore' в коді в іншому місці?
KenV99

Ви можете визначити будь-яку функцію з будь-яким вподобаним іменем, перш ніж викликати функцію copytree. Ця функція (яка також може бути лямбда-виразом) бере два аргументи: ім'я каталогу та файли в ньому, вона повинна повертати ітерабельні файли ігнорування.
Кирилла Понтьє

[x for x in lst if x not in excl]це не те саме, що copytree, яке використовує глобальну відповідність шаблону. 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
@Jay, лише якщо каталог вже існує. Я dirs_exist_okвийшов із першого дзвінка, щоб проілюструвати різницю (і тому, що каталог ще не існує в прикладі ОП), але, звичайно, ви можете використовувати його, якщо хочете.
Кріс

Дякую, якщо ви додасте коментар біля першої копії, я думаю, це зробить зрозумілішим :)
Jay

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)

Відкоментуйте Rmtree, щоб зробити його функцією переміщення.


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.

updateonlyявляє собою bool, якщо True, буде копіювати файли лише з модифікованими датами, новішими, ніж існуючі файли, dstякщо не вказано, в forceupdateякому буде скопійовано незалежно.

ignoreі forceupdateочікуйте списків імен файлів або папок / назви файлів відносно src та прийміть підстановочні символи у стилі Unix, подібні до globабо fnmatch.

Функція повертає список файлів, скопійованих (або буде скопійовано, якщо dryrunце правда).

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

я б припустив, що найшвидшим і найпростішим способом було б, щоб пітон викликав системні команди ...

приклад ..

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

Тар і gzip вгору до каталогу .... розпакуйте та зніміть знімок каталогу у потрібному місці.

так?


якщо ви працюєте у Windows ... завантажте 7zip .. і використовуйте для цього командний рядок. ... знову лише пропозиції.
Кірбі

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