Copia file o directory in modo ricorsivo in Python


116

Python sembra avere funzioni per copiare file (ad esempio shutil.copy) e funzioni per copiare directory (ad esempio shutil.copytree) ma non ho trovato alcuna funzione che gestisca entrambi. Certo, è banale controllare se si desidera copiare un file o una directory, ma sembra una strana omissione.

Non esiste davvero una funzione standard che funzioni come il cp -rcomando unix , cioè supporta sia le directory che i file e le copie in modo ricorsivo? Quale sarebbe il modo più elegante per aggirare questo problema in Python?


3
Sì, questo è un casino. Uno dei posti in cui, cercando di riflettere le chiamate di sistema sottostanti, Python peggiora l'interfaccia visibile. Sebbene non sia difficile passare dalla copia-file alla copia-albero, non avrebbe dovuto essere necessario. Forse archiviare una richiesta di miglioramento sul bug tracker di Python per consentire copytreedi copiare un singolo file?
bobince

Risposte:


142

Ti suggerisco di chiamare prima shutil.copytreee, se viene generata un'eccezione, riprova 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

18
Penso che sarebbe molto più pulito controllare semplicemente se src è una directory che utilizza os.path.isdir (src) invece di catturare un'eccezione come questa. O c'è qualche motivo speciale per cui si dovrebbe invece usare un'eccezione?
pafcu

31
1) Perché nel mondo Python EAFP (è più facile chiedere perdono che permesso) è preferito a LBYL (guarda prima di saltare). Posso fornirti link a riguardo, se ti sembra nuovo. 2) La funzione di libreria lo verifica già indirettamente, quindi perché replicare il controllo? 3) nulla impedisce alla shutil.copytreefunzione di migliorare e gestire entrambi i casi in futuro. 4) Le eccezioni non sono così eccezionali in Python; ad esempio, un'iterazione si interrompe lanciando un'eccezione StopIteration.
tzot

2
Bene, in questo caso la gestione dell'eccezione richiede 6 righe, mentre il controllo del tipo richiede 4 righe. Non molto, ma alla fine si somma. Inoltre, come dici tu, un giorno copytree potrebbe supportare molto bene anche i file. Ma è impossibile dire come sarà l'implementazione. Forse genera un'eccezione in alcune circostanze in cui la copia funziona? In tal caso il mio codice smetterebbe improvvisamente di funzionare solo a causa della funzionalità aggiunta. Ma probabilmente hai ragione, le eccezioni sono abbastanza comuni in Python, qualcosa che trovo molto fastidioso, ma probabilmente è perché sembra che non mi ci
abitui

5
In realtà le eccezioni hanno un chiaro vantaggio oggettivo in questo caso: è del tutto possibile (sebbene altamente improbabile) che il tipo cambi tra il controllo e la chiamata alla funzione corretta.
pafcu

2
a mio parere personale, aggiungere funzionalità di base in un'eccezione è una cattiva pratica, non importa quale lingua si sta utilizzando. mette la funzionalità in un posto dove molti sviluppatori non cercheranno. inoltre, se non scrivi un commento, uno sviluppatore Python meno esperto non capirebbe veramente quale sia lo scopo di questo nuovo tentativo. e se hai bisogno di aggiungere un commento per una cosa così banale come qui, qualcosa nello stile del tuo codice è sbagliato. infine, scrivere un if / else risulterà in un codice molto più facile da leggere.
questo.myself

7

Per aggiungere risposte Tzot e gns , ecco un modo alternativo per copiare file e cartelle in modo ricorsivo. (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)

Se è la prima volta e non hai idea di come copiare file e cartelle in modo ricorsivo, spero che questo aiuti.


3

shutil.copye shutil.copy2stanno copiando file.

shutil.copytreecopia una cartella con tutti i file e tutte le sottocartelle. shutil.copytreesta usando shutil.copy2per copiare i file.

Quindi l'analogo a quello cp -rche stai dicendo è shutil.copytreeperché prende di cp -rmira e copia una cartella e i suoi file / sottocartelle come shutil.copytree. Senza le -r cpcopie i file piacciono shutil.copye shutil.copy2fanno.


1
Non credo che tu abbia capito la domanda. Prova shutil.copytree('C:\myfile.txt', 'C:\otherfile'). Non funziona. Questo è quello che chiedeva l'OP ... 7 anni fa.
Jean-François Corbett

Ovviamente non funziona. Come cp non funziona con le cartelle. Hai bisogno di un'opzione speciale. copy e copytree sono il modo migliore per gestire la copia. Se copytree potesse targetizzare e file sarebbe facile commettere errori. Devi sapere cosa stai prendendo di mira sia con Linux che con Python. È difficile. Inoltre qualcun altro l'ha commentato qui, ma vedendo la domanda e le risposte non ha potuto resistere alla risposta. Il modo elegante è sapere cosa vuoi fare e non una copia universale senza alcun controllo.
GM

2

Unix cpnon "supporta sia le directory che i file":

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

Per fare in modo che cp copi una directory, devi dire manualmente a cp che è una directory, usando il flag '-r'.

C'è però qualche disconnessione qui - cp -rquando viene passato un nome di file come sorgente copierà felicemente solo il singolo file; copytree non lo farà.


2
docs.python.org/library/shutil.html include il codice per copytree () che dimostra la gestione di file ordinari, collegamenti simbolici e directory.
James Polley

1
Questa risposta non risponde alla domanda. Dovrebbe essere un commento, non una risposta.
Jean-François Corbett


-2

Il metodo python shutil.copytree è un casino. Ne ho fatto uno che funziona correttamente:

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('..')

1
Questo codice è utile per il controllo dei singoli file di lavoro (verifica del problema di sovrascrittura), ma non funzionerà per i file binari come "zip". Perché non usare una semplice copia di file Python invece della lettura / scrittura riga per riga?
notilas
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.