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