Copier un fichier ou des répertoires de manière récursive en Python

116

Python semble avoir des fonctions pour copier des fichiers (par exemple shutil.copy) et des fonctions pour copier des répertoires (par exemple shutil.copytree) mais je n'ai trouvé aucune fonction qui gère les deux. Bien sûr, il est trivial de vérifier si vous souhaitez copier un fichier ou un répertoire, mais cela semble être une étrange omission.

N'existe-t-il pas vraiment de fonction standard qui fonctionne comme la cp -rcommande unix , c'est-à-dire qui prend en charge à la fois les répertoires et les fichiers et les copie récursivement? Quelle serait la manière la plus élégante de contourner ce problème en Python?

pafcu
la source
3
Oui, c'est un gâchis. L'un des endroits où, en essayant de refléter les appels système sous-jacents, Python aggrave l'interface visible. Bien qu'il ne soit pas difficile de basculer entre le fichier de copie et l'arborescence de copie, cela n'aurait pas dû être nécessaire. Peut-être déposer une demande d'amélioration sur le traqueur de bogues Python pour permettre copytreede copier un seul fichier?
bobince le

Réponses:

142

Je vous suggère d'appeler d'abord shutil.copytree, et si une exception est levée, réessayez avec 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
la source
18
Je pense qu'il serait beaucoup plus simple de vérifier simplement si src est un répertoire utilisant os.path.isdir (src) au lieu d'attraper une exception comme celle-ci. Ou y a-t-il une raison particulière pour laquelle on devrait utiliser une exception ici à la place?
pafcu
31
1) Parce que dans le monde Python, l'EAFP (il est plus facile de demander pardon que la permission) est préféré à LBYL (regardez avant de sauter). Je peux vous fournir des liens à ce sujet, si cela vous semble nouveau. 2) La fonction de bibliothèque vérifie déjà indirectement cela, alors pourquoi répliquer le contrôle? 3) rien n'empêche la shutil.copytreefonction d'améliorer et de gérer les deux cas à l'avenir. 4) Les exceptions ne sont pas si exceptionnelles en Python; Par exemple, une itération s'arrête en lançant une exception StopIteration.
tzot le
2
Eh bien, dans ce cas, la gestion de l'exception prend 6 lignes, tandis que la vérification du type prend 4 lignes. Pas grand chose, mais ça s'additionne à la fin. De plus, comme vous le dites, copytree pourrait un jour très bien prendre en charge les fichiers. Mais il est impossible de dire à quoi ressemblera cette implémentation. Peut-être que cela lève une exception dans certaines circonstances où la copie fonctionne? Dans ce cas, mon code cesserait soudainement de fonctionner simplement à cause de la fonctionnalité ajoutée. Mais vous avez probablement raison, les exceptions sont assez fréquentes en Python, ce que je trouve très ennuyeux, mais c'est probablement parce que je ne semble jamais m'y habituer
pafcu
5
En fait, les exceptions ont un avantage objectif clair dans ce cas: il est tout à fait possible (bien que hautement improbable) que le type change entre la vérification et l'appel à la fonction correcte.
pafcu du
2
à mon avis personnel, ajouter des fonctionnalités de base dans une exception est une mauvaise pratique, peu importe la langue que vous utilisez. il met la fonctionnalité dans un endroit où beaucoup de développeurs ne chercheront pas. de plus, si vous n'écrivez pas de commentaire, un développeur python moins expérimenté ne comprendrait pas vraiment le but de cette nouvelle tentative. et si vous avez besoin d'ajouter un commentaire pour une chose aussi triviale comme ici, quelque chose ne va pas dans votre style de code. enfin, écrire un if / else se traduira par un code beaucoup plus facile à lire.
this.myself
7

Pour ajouter les réponses de Tzot et gns , voici une autre façon de copier des fichiers et des dossiers de manière récursive. (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 c'est votre première fois et que vous ne savez pas comment copier des fichiers et des dossiers de manière récursive, j'espère que cela vous aidera.

mondieki
la source
3

shutil.copyet shutil.copy2copient des fichiers.

shutil.copytreecopie un dossier avec tous les fichiers et tous les sous-dossiers. shutil.copytreeutilise shutil.copy2pour copier les fichiers.

Donc, l'analogue que cp -rvous dites est le shutil.copytreeparce que cp -rcible et copie un dossier et ses fichiers / sous-dossiers comme shutil.copytree. Sans les -r cpcopies de fichiers comme shutil.copyet shutil.copy2faire.

gms
la source
1
Je ne pense pas que vous ayez compris la question. Essayez shutil.copytree('C:\myfile.txt', 'C:\otherfile'). Ça ne marche pas. C'est ce que le PO demandait ... il y a 7 ans.
Jean-François Corbett
Bien sûr, cela ne fonctionne pas. Comme cp ne fonctionne pas avec les dossiers. Vous avez besoin d'une option spéciale. copy et copytree sont le meilleur moyen de gérer la copie. Si copytree pouvait cibler et fichiers, il serait facile de faire des erreurs. Vous devez savoir ce que vous ciblez à la fois avec Linux et Python. C'est dur. De plus, quelqu'un d'autre l'a commenté ici, mais voyant la question et les réponses ne pouvaient pas résister à la réponse. La manière élégante est de savoir ce que vous voulez faire et non une copie universelle sans aucun contrôle.
gms
2

Unix cpne `` prend pas en charge à la fois les répertoires et les fichiers '':

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

Pour que cp copie un répertoire, vous devez indiquer manuellement à cp qu'il s'agit d'un répertoire, en utilisant l'indicateur '-r'.

Il y a cependant une certaine déconnexion ici - une cp -rfois passé un nom de fichier, la source copiera volontiers le seul fichier; copytree ne le fera pas.

James Polley
la source
2
docs.python.org/library/shutil.html inclut le code de copytree () qui illustre la gestion des fichiers ordinaires, des liens symboliques et des répertoires.
James Polley
1
Cette réponse ne répond pas à la question. Ce devrait être un commentaire, pas une réponse.
Jean-François Corbett
0

Je pense que copy_tree est ce que vous recherchez

algorythmes
la source
-2

La méthode python shutil.copytree est un gâchis. J'en ai fait un qui fonctionne correctement:

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
la source
1
Ce code est utile pour la vérification de fichiers individuels (problème d'écrasement), mais ne fonctionnera pas pour les fichiers binaires tels que «zip». Pourquoi ne pas utiliser une simple copie de fichier python au lieu d'une lecture / écriture ligne par ligne?
notilas