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