¿Cómo copio un directorio completo de archivos en un directorio existente usando Python?

210

Ejecute el siguiente código desde un directorio que contiene un directorio llamado bar(que contiene uno o más archivos) y un directorio llamado baz(que también contiene uno o más archivos). Asegúrese de que no haya un directorio llamado foo.

import shutil
shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo')

Fallará con:

$ 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'

Quiero que esto funcione de la misma manera que si hubiera escrito:

$ mkdir foo
$ cp bar/* foo/
$ cp baz/* foo/

¿Es necesario utilizar shutil.copy()para copiar cada archivo en baza foo? (¿Después de que ya he copiado el contenido de 'bar' en 'foo' con shutil.copytree()?) ¿O hay una manera más fácil / mejor?

Daryl Spitzer
fuente
1
FYI: aquí está la función original de copytree, simplemente
cópiela
3
Hay un problema de Python acerca de cambiar shutil.copytree()el comportamiento para permitir la escritura en un directorio existente, pero hay algunos detalles de comportamiento que deben acordarse.
Nick Chammas
2
Solo señalando que la solicitud de mejora mencionada anteriormente se ha implementado para Python 3.8: docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan

Respuestas:

174

Esta limitación del estándar shutil.copytreeparece arbitraria y molesta. Solución alterna:

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)

Tenga en cuenta que no es totalmente coherente con el estándar copytree:

  • No honra symlinksy ignoreparámetros para el directorio raíz del srcárbol;
  • no genera shutil.Errorerrores en el nivel raíz de src;
  • en caso de errores durante la copia de un subárbol, aumentará shutil.Errorpara ese subárbol en lugar de intentar copiar otros subárboles y generará un solo combinado shutil.Error.
atzz
fuente
50
¡Gracias! ¡De acuerdo en que esto parece totalmente arbitrario! shutil.copytreehace un os.makedirs(dst)al principio. Ninguna parte del código realmente tendría un problema con un directorio preexistente. Esto necesita ser cambiado. Al menos proporcione un exist_ok=Falseparámetro para la llamada
cfi del
66
Esta es una buena respuesta, sin embargo, vale la pena ver también la respuesta de Mital Vora a continuación. Han llamado a copytree recursivamente en lugar de llamar a shutil.copytree () porque de lo contrario surgirá el mismo problema. Posiblemente considere fusionar respuestas o actualizar a Mital Vora.
PJeffes
44
Esto falla si se le da una ruta que incluye un directorio que no está vacío en el destino. Tal vez alguien podría resolver esto con la recursividad de cola, pero aquí hay una modificación a su código que funcionadef 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
Meh, súper molesto. Han pasado 4 años y shutil.copytree todavía tiene esta restricción tonta. :-(
antred
55
@antred ... pero distutils.dir_util.copy_tree(), que también reside en stdlib, no tiene esa restricción y en realidad se comporta como se esperaba. Dado eso, no hay una razón convincente para intentar desenrollar su propia implementación ( ... normalmente rota ). La respuesta de Brendan Abel debería ser absolutamente la solución aceptada ahora.
Cecil Curry
257

Aquí hay una solución que forma parte de la biblioteca estándar:

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

Ver esta pregunta similar.

Copie el contenido del directorio en un directorio con python

Brendan Abel
fuente
55
Esta es buena porque usa la biblioteca estándar. Los enlaces simbólicos, el modo y el tiempo también se pueden conservar.
itsafire
1
Notó una pequeña desventaja. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string, es decir, no acepta PosixPath. Necesitará str(PosixPath). Lista de deseos para mejorar. Aparte de este asunto, prefiero esta respuesta.
Sun Bear
@SunBear, sí, creo que ese será el caso con la mayoría de las otras bibliotecas que toman caminos como cadenas. Parte de la desventaja de elegir no hacer que el Pathobjeto herede str, supongo, como la mayoría de las implementaciones anteriores de objetos de ruta orientados a objetos ...
Brendan Abel
Por cierto, me encontré con una deficiencia documentada de esta función. Está documentado aquí . Los usuarios de esta función fueron consejos para estar al tanto.
Sun Bear
1
Si bien es "técnicamente público", tenga en cuenta que los desarrolladores de distutils dejaron en claro (mismo enlace que @ SunBear's, thx!) Que distutils.dir_util.copy_tree()se considera un detalle de implementación de distutils y no se recomienda para uso público. La solución real debería ser shutil.copytree()mejorar / ampliar para comportarse más como distutils.dir_util.copy_tree(), pero sin sus defectos. Mientras tanto, seguiré usando funciones de ayuda personalizadas similares a algunas de las que se proporcionan en otras respuestas.
Boris Dalstein
61

En una ligera mejora en la respuesta de atzz a la función donde la función anterior siempre intenta copiar los archivos de origen a destino.

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)

En mi implementación anterior

  • Crear el directorio de salida si aún no existe
  • Haciendo el directorio de copia llamando recursivamente a mi propio método.
  • Cuando llegamos a copiar el archivo, verifico si el archivo está modificado, entonces solo debemos copiarlo.

Estoy usando la función anterior junto con scons build. Me ayudó mucho ya que cada vez que compilo no necesito copiar todo el conjunto de archivos ... sino solo los archivos que se modifican.

Mora Vora
fuente
44
Bien, excepto que tienes enlaces simbólicos e ignoras como argumentos, pero se ignoran.
Matthew Alpert el
Vale la pena señalar que la granularidad st_mtime puede ser tan gruesa como 2 segundos en los sistemas de archivos FAT docs.python.org/2/library/os.html . Al usar este código en un contexto donde las actualizaciones ocurren en rápida sucesión, es posible que no se realicen modificaciones.
dgh
Hay un error en la penúltima línea, debe ser: if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec
34

Una fusión inspirada en atzz y 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)
  • Mismo comportamiento que shutil.copytree , con enlaces simbólicos e ignorar parámetros
  • Crear estructura de destino de directorio si no existe
  • No fallará si dst ya existe
Cyrille Pontvieux
fuente
Esto es mucho más rápido que la solución original cuando el anidamiento del directorio es profundo. Gracias
Kashif
¿Definió una función también llamada 'ignorar' en el código en otro lugar?
KenV99
Puede definir cualquier función con el nombre que desee antes de llamar a la función copytree. Esta función (que también podría ser una expresión lambda) toma dos argumentos: un nombre de directorio y los archivos que contiene, debe devolver un iterativo de ignorar archivos.
Cyrille Pontvieux
[x for x in lst if x not in excl]esto no hace lo mismo que copytree, que utiliza la coincidencia de patrón global. en.wikipedia.org/wiki/Glob_(programming)
Konstantin Schubert
2
Esto es genial. El ignorar no se estaba utilizando correctamente en la respuesta anterior.
Keith Holliday
21

Python 3.8 introdujo el dirs_exist_okargumento para shutil.copytree:

Copie recursivamente un árbol de directorios completo rooteado en src a un directorio llamado dst y devuelva el directorio de destino. dirs_exist_ok dicta si se debe generar una excepción en caso de que dst o algún directorio padre faltante ya exista.

Por lo tanto, con Python 3.8+ esto debería funcionar:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)
Chris
fuente
dirs_exist_ok=Falsepor defecto en copytree, ¿no fallará el primer intento de copia?
Jay
1
@ Jay, solo si el directorio ya existe. Me salí dirs_exist_okde la primera llamada para ilustrar la diferencia (y porque el directorio aún no existe en el ejemplo de OP), pero, por supuesto, puede usarlo si lo desea.
Chris
Gracias, si agrega un comentario cerca de la primera copia, creo que sería más claro :)
Jay
7

los documentos declaran explícitamente que el directorio de destino no debería existir :

El directorio de destino, nombrado por dst, no debe existir ya; se creará así como los directorios principales que faltan.

Creo que su mejor opción es os.walkel segundo y todos los directorios, copy2directorios y archivos consecuentes y hacer más copystatpara directorios. Después de todo, eso es precisamente lo copytreeque se explica en los documentos. O podría copyy copystatcada directorio / archivo y en os.listdirlugar de os.walk.

SilentGhost
fuente
1

Esto está inspirado en la mejor respuesta original proporcionada por atzz, acabo de agregar reemplazar la lógica de archivo / carpeta. Por lo tanto, en realidad no se fusiona, pero elimina el archivo / carpeta existente y copia el nuevo:

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)

Descomente el rmtree para que sea una función de movimiento.

radtek
fuente
0

Aquí está mi versión de la misma tarea ::

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)
Barmaley
fuente
0

Aquí hay una versión inspirada en este hilo que imita más de cerca distutils.file_util.copy_file.

updateonlyes un bool si es True, solo copiará archivos con fechas modificadas más recientes que los archivos existentes a dstmenos que se enumeren en los forceupdatecuales se copiará independientemente.

ignorey forceupdateesperar listas de nombres de archivos o carpetas / nombres de archivos relativos src y aceptar comodines de estilo Unix similares a globo fnmatch.

La función devuelve una lista de archivos copiados (o se copiarían si dryrunfuera Verdadero).

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
KenV99
fuente
0

La solución anterior tiene algún problema que srcpuede sobrescribirse dstsin ninguna notificación o excepción.

Agrego un predict_errormétodo para predecir errores antes de copiar. copytreeprincipalmente basado en la versión de Cyrille Pontvieux.

Lo mejor es usar predict_errorpara predecir todos los errores al principio, a menos que desee ver una excepción generada una por otra cuando se ejecute copytreehasta corregir todos los errores.

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)
Mitril
fuente
0

Aquí está mi pase al problema. Modifiqué el código fuente de copytree para mantener la funcionalidad original, pero ahora no se produce ningún error cuando el directorio ya existe. También lo cambié para que no sobrescriba los archivos existentes, sino que mantenga ambas copias, una con un nombre modificado, ya que esto era importante para mi aplicación.

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)
James
fuente
0

Prueba esto:

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")
Ahmed
fuente
0

Aquí hay una versión que espera una pathlib.Pathentrada.

# 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))

Tenga en cuenta que esta función requiere Python 3.6, que es la primera versión de Python donde os.listdir()admite objetos de tipo ruta como entrada. Si necesita admitir versiones anteriores de Python, puede reemplazar listdir(src)por listdir(str(src)).

Boris Dalstein
fuente
-2

Supongo que la forma más rápida y simple sería hacer que Python llame a los comandos del sistema ...

ejemplo..

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

Tar y gzip el directorio .... descomprima y descomprima el directorio en el lugar deseado.

yah

Kirby
fuente
si está ejecutando en Windows ... descargue 7zip ... y use la línea de comando para eso. ... de nuevo solo sugerencias.
Kirby
31
Los comandos del sistema siempre deben ser el último recurso. Siempre es mejor utilizar la biblioteca estándar siempre que sea posible para que su código sea portátil.
jathanism