Copie archivos o directorios de forma recursiva en Python

116

Python parece tener funciones para copiar archivos (por ejemplo shutil.copy) y funciones para copiar directorios (por ejemplo shutil.copytree), pero no he encontrado ninguna función que maneje ambos. Claro, es trivial comprobar si desea copiar un archivo o un directorio, pero parece una extraña omisión.

¿Realmente no existe una función estándar que funcione como el cp -rcomando de Unix , es decir, que admita directorios y archivos y realice copias de forma recursiva? ¿Cuál sería la forma más elegante de solucionar este problema en Python?

pafcu
fuente
3
Sí, esto es un desastre. Uno de los lugares donde, al tratar de reflejar las llamadas al sistema subyacentes, Python empeora la interfaz visible. Aunque no es difícil cambiar entre copy-file y copy-tree, no debería haber sido necesario. ¿Quizás presentar una solicitud de mejora en el rastreador de errores de Python para permitir copytreecopiar un solo archivo?
Bobince

Respuestas:

142

Le sugiero que primero llame shutil.copytreey, si se lanza una excepción, vuelva a intentarlo con shutil.copy.

import shutil, errno

def copyanything(src, dst):
    try:
        shutil.copytree(src, dst)
    except OSError as exc: # python >2.5
        if exc.errno == errno.ENOTDIR:
            shutil.copy(src, dst)
        else: raise
tzot
fuente
18
Creo que sería mucho más limpio simplemente verificar si src es un directorio que usa os.path.isdir (src) en lugar de detectar una excepción como esta. ¿O hay alguna razón especial por la que uno debería usar una excepción aquí en su lugar?
pafcu
31
1) Porque en el mundo Python se prefiere EAFP (es más fácil pedir perdón que permiso) a LBYL (mirar antes de saltar). Puedo proporcionarle enlaces sobre eso, si le parece nuevo. 2) La función de biblioteca ya verifica indirectamente eso, entonces, ¿por qué replicar la verificación? 3) nada impide que la shutil.copytreefunción mejore y gestione ambos casos en el futuro. 4) Las excepciones no son tan excepcionales en Python; por ejemplo, una iteración se detiene lanzando una excepción StopIteration.
tzot
2
Bueno, en este caso, el manejo de la excepción toma 6 líneas, mientras que la verificación del tipo toma 4 líneas. No mucho, pero al final se suma. Además, como usted dice, copytree algún día también podría admitir archivos. Pero es imposible saber cómo será esa implementación. ¿Quizás arroja una excepción en alguna circunstancia en la que funciona la copia? En ese caso, mi código dejaría de funcionar repentinamente solo por la funcionalidad agregada. Pero probablemente tenga razón, las excepciones son bastante comunes en Python, algo que encuentro muy molesto, pero probablemente sea porque nunca parece que me acostumbre a ello
pafcu
5
En realidad, las excepciones tienen una clara ventaja objetiva en este caso: es completamente posible (aunque muy poco probable) que el tipo cambie entre la verificación y la llamada a la función correcta.
Pafcu
2
en mi opinión personal, agregar funcionalidad básica en un excepto es una mala práctica, sin importar qué idioma esté usando. coloca la funcionalidad en un lugar donde muchos desarrolladores no buscarán. Además, si no escribe un comentario, un desarrollador de Python con menos experiencia no entendería realmente cuál es el propósito de este reintento. y si necesita agregar un comentario para algo tan trivial como aquí, algo en su estilo de código está mal. finalmente, escribir un if / else resultará en un código mucho más fácil de leer.
this.myself
7

Para agregar las respuestas de Tzot y gns , aquí hay una forma alternativa de copiar archivos y carpetas de forma recursiva. (Python 3.X)

import os, shutil

root_src_dir = r'C:\MyMusic'    #Path/Location of the source directory
root_dst_dir = 'D:MusicBackUp'  #Path to the destination folder

for src_dir, dirs, files in os.walk(root_src_dir):
    dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
    if not os.path.exists(dst_dir):
        os.makedirs(dst_dir)
    for file_ in files:
        src_file = os.path.join(src_dir, file_)
        dst_file = os.path.join(dst_dir, file_)
        if os.path.exists(dst_file):
            os.remove(dst_file)
        shutil.copy(src_file, dst_dir)

Si es tu primera vez y no tienes idea de cómo copiar archivos y carpetas de forma recursiva, espero que esto te ayude.

mondieki
fuente
3

shutil.copyy shutil.copy2están copiando archivos.

shutil.copytreecopia una carpeta con todos los archivos y todas las subcarpetas. shutil.copytreeestá utilizando shutil.copy2para copiar los archivos.

Entonces, lo análogo a lo cp -rque estás diciendo es shutil.copytreeporque cp -rapunta y copia una carpeta y sus archivos / subcarpetas como shutil.copytree. Sin los -r cparchivos de copias como shutil.copyy shutil.copy2hacer.

gms
fuente
1
No creo que hayas entendido la pregunta. Prueba shutil.copytree('C:\myfile.txt', 'C:\otherfile'). No funciona. Eso es lo que preguntaba el OP ... hace 7 años.
Jean-François Corbett
Por supuesto que no funciona. Como cp no funciona con carpetas. Necesitas una opción especial. copy y copytree son la mejor manera de manejar la copia. Si copytree pudiera apuntar y archivos, sería fácil cometer errores. Debe saber a qué se dirige tanto con Linux como con Python. Así de dificil. Además, alguien más lo comentó aquí, pero al ver la pregunta y las respuestas no pude resistir la tentación de responder. La forma elegante es saber lo que quiere hacer y no una copia universal sin ningún control.
gms
2

Unix cpno 'admite tanto directorios como archivos':

betelgeuse:tmp james$ cp source/ dest/
cp: source/ is a directory (not copied).

Para hacer que cp copie un directorio, debe decirle manualmente a cp que es un directorio, usando el indicador '-r'.

Sin embargo, hay algo de desconexión aquí: cp -rcuando se le pasa un nombre de archivo, la fuente copiará felizmente solo el archivo; copytree no lo hará.

James Polley
fuente
2
docs.python.org/library/shutil.html incluye el código para copytree () que demuestra el manejo de archivos, enlaces simbólicos y directorios normales.
James Polley
1
Esta respuesta no responde a la pregunta. Debería ser un comentario, no una respuesta.
Jean-François Corbett
0

Creo que copy_tree es lo que estás buscando.

algoritmos
fuente
-2

El método python shutil.copytree es un desastre. He hecho uno que funciona correctamente:

def copydirectorykut(src, dst):
    os.chdir(dst)
    list=os.listdir(src)
    nom= src+'.txt'
    fitx= open(nom, 'w')

    for item in list:
        fitx.write("%s\n" % item)
    fitx.close()

    f = open(nom,'r')
    for line in f.readlines():
        if "." in line:
            shutil.copy(src+'/'+line[:-1],dst+'/'+line[:-1])
        else:
            if not os.path.exists(dst+'/'+line[:-1]):
                os.makedirs(dst+'/'+line[:-1])
                copydirectorykut(src+'/'+line[:-1],dst+'/'+line[:-1])
            copydirectorykut(src+'/'+line[:-1],dst+'/'+line[:-1])
    f.close()
    os.remove(nom)
    os.chdir('..')
Kutenzo
fuente
1
Este código es bueno para la verificación de archivos individuales de trabajo (verifique el problema de sobrescritura), pero no funcionará para archivos binarios como 'zip'. ¿Por qué no usar una copia de archivo de Python simple en lugar de leer / escribir línea por línea?
notilas