Как мне скопировать весь каталог файлов в существующий каталог с помощью 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()
?) Или есть более простой / лучший способ?
Решение
Это ограничение стандарта 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)
Обратите внимание, что это не совсем соответствует стандартному дереву копирования:
- это не делает чести
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.Это мне очень помогло, так как каждый раз при компиляции мне может не понадобиться копировать весь набор файлов..но только те файлы, которые были изменены.
Слияние, вдохновленное 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
, не должно уже существовать;он будет создан, а также отсутствующие родительские каталоги.
Я думаю, тебе лучше всего os.walk
второй и все последующие каталоги, copy2
каталог и файлы и выполните дополнительные copystat
для каталогов.Ведь именно это copytree
делает, как описано в документации.Или ты мог бы copy
и copystat
каждый каталог/файл и os.listdir
вместо os.walk
.
Вы можете изменить shutil
и получить эффект (в моей версии shutil
это онлайн 315
)
Изменять
os.makedirs(dst)
К
os.makedirs(dst,exist_ok=True)
Это вдохновлено исходным лучшим ответом, предоставленным 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, чтобы сделать его функцией перемещения.
Я бы предположил, что самым быстрым и простым способом будет вызов системных команд Python...
пример..
import os
cmd = '<command line call>'
os.system(cmd)
Tar и заархивируйте каталог....разархивируйте и разархивируйте каталог в нужном месте.
да?
Вот моя версия той же задачи:
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
является логическим значением, если 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
Предыдущее решение имеет некоторую проблему, которая 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)
Вот мой подход к проблеме.Я изменил исходный код 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)
Попробуй это:
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")