Comment copier un répertoire entier de fichiers dans un répertoire existant à l'aide de Python?

210

Exécutez le code suivant à partir d'un répertoire contenant un répertoire nommé bar(contenant un ou plusieurs fichiers) et un répertoire nommé baz(contenant également un ou plusieurs fichiers). Assurez-vous qu'il n'y a pas de répertoire nommé foo.

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

Il échouera avec:

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

Je veux que cela fonctionne de la même manière que si j'avais tapé:

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

Dois-je utiliser shutil.copy()pour copier chaque fichier bazdans foo? (Après avoir déjà copié le contenu de 'bar' dans 'foo' avec shutil.copytree()?) Ou existe-t-il un moyen plus facile / meilleur?

Daryl Spitzer
la source
1
Pour info: voici la fonction originale de copytree, il suffit de la copier et de la patcher :)
schlamar
3
Il y a un problème Python sur le changement shutil.copytree()de comportement pour permettre l'écriture dans un répertoire existant, mais il y a certains détails de comportement qui doivent être convenus.
Nick Chammas
2
Juste en notant que la demande d'amélioration mentionnée ci-dessus a été implémentée pour Python 3.8: docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan

Réponses:

174

Cette limitation de la norme shutil.copytreesemble arbitraire et ennuyeuse. Solution de contournement:

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)

Notez qu'il n'est pas entièrement conforme à la norme copytree:

  • il ne respecte pas symlinkset ignoreparamètres pour le répertoire racine de l' srcarbre;
  • il ne soulève pas shutil.Errord'erreurs au niveau racine de src;
  • en cas d'erreurs lors de la copie d'un sous-arbre, il se lèvera shutil.Errorpour ce sous-arbre au lieu d'essayer de copier d'autres sous-arbres et de lever un seul combiné shutil.Error.
atzz
la source
50
Merci! Convenez que cela semble totalement arbitraire! shutil.copytreefait un os.makedirs(dst)au début. Aucune partie du code n'aurait réellement un problème avec un répertoire préexistant. Cela doit être changé. Fournir au moins un exist_ok=Falseparamètre à l'appel
cfi
6
C'est une bonne réponse - mais la réponse de Mital Vora ci-dessous mérite également d'être examinée. Ils ont appelé copytree récursivement plutôt que d'appeler shutil.copytree () car le même problème se poserait autrement. Envisagez éventuellement de fusionner les réponses ou de mettre à jour celles de Mital Vora.
PJeffes
4
Cela échoue si on lui donne un chemin qui inclut un répertoire qui n'est pas vide dans la destination. Peut-être que quelqu'un pourrait résoudre ce problème avec la récursivité de la queue, mais voici une modification de votre code qui fonctionnedef 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, super ennuyeux. C'est 4 ans plus tard, et shutil.copytree a toujours cette restriction idiote. :-(
entrée le
5
@antred ... mais distutils.dir_util.copy_tree(), qui réside également dans le stdlib, n'a pas une telle restriction et se comporte en fait comme prévu. Compte tenu de cela, il n'y a aucune raison impérieuse d'essayer de dérouler votre propre implémentation ( ... généralement cassée ). La réponse de Brendan Abel devrait absolument être la solution acceptée maintenant.
Cecil Curry
257

Voici une solution qui fait partie de la bibliothèque standard:

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

Voir cette question similaire.

Copiez le contenu du répertoire dans un répertoire avec python

Brendan Abel
la source
5
C'est un bon parce qu'il utilise la bibliothèque standard. Les liens symboliques, le mode et l'heure peuvent également être conservés.
itsafire
1
Remarqué un petit inconvénient. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string, c'est-à-dire qu'il n'accepte pas PosixPath. Besoin de str(PosixPath). Liste de souhaits d'amélioration. En dehors de cette question, je préfère cette réponse.
Sun Bear
@SunBear, Ouais, je pense que ce sera le cas avec la plupart des autres bibliothèques qui prennent des chemins comme des chaînes. Une partie de l'inconvénient de choisir de ne pas faire Pathhériter l' objet, strje suppose, comme la plupart des implémentations antérieures d'objets de chemin orienté objet.
Brendan Abel
Btw, je suis tombé sur une déficience documentée de cette fonction. C'est documenté ici . Les utilisateurs de cette fonction ont été conseillés d'en prendre connaissance.
Sun Bear
1
Bien que "techniquement public", veuillez noter que les développeurs de distutils ont clairement indiqué (même lien que @ SunBear's, thx!) Qui distutils.dir_util.copy_tree()est considéré comme un détail d'implémentation de distutils et non recommandé pour une utilisation publique. La vraie solution devrait être shutil.copytree()d'être améliorée / étendue pour se comporter davantage distutils.dir_util.copy_tree(), mais sans ses défauts. En attendant, je continuerai à utiliser des fonctions d'assistance personnalisées similaires à certaines de celles fournies dans d'autres réponses.
Boris Dalstein
61

En légère amélioration sur la réponse d'Atzz à la fonction où la fonction ci-dessus essaie toujours de copier les fichiers de la source vers la destination.

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)

Dans ma mise en œuvre ci-dessus

  • Création du répertoire de sortie s'il n'existe pas déjà
  • Faire le répertoire de copie en appelant récursivement ma propre méthode.
  • Lorsque nous arrivons à copier réellement le fichier, je vérifie si le fichier est modifié, alors seulement nous devons copier.

J'utilise la fonction ci-dessus avec la construction scons. Cela m'a beaucoup aidé car à chaque fois que je compile, je n'ai peut-être pas besoin de copier l'ensemble des fichiers .. mais uniquement les fichiers qui sont modifiés.

Mital Vora
la source
4
Bien, sauf que vous avez des liens symboliques et que vous ignorez comme arguments, mais ils sont ignorés.
Matthew Alpert
Il convient de noter que la granularité st_mtime peut être aussi grossière que 2 secondes sur les systèmes de fichiers FAT docs.python.org/2/library/os.html . En utilisant ce code dans un contexte où les mises à jour se produisent en succession rapide, vous pouvez constater que des remplacements n'ont pas lieu.
dgh
Il y a un bug dans l'avant-dernière ligne, devrait être: if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec
34

Une fusion inspirée par atzz et 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)
  • Comportement identique à shutil.copytree , avec des liens symboliques et ignorer les paramètres
  • Créer une structure de destination de répertoire si elle n'existe pas
  • N'échouera pas si dst existe déjà
Cyrille Pontvieux
la source
C'est beaucoup plus rapide que la solution d'origine lorsque l'imbrication de répertoire est profonde. Merci
Kashif
Avez-vous défini une fonction également nommée «ignorer» dans le code ailleurs?
KenV99
Vous pouvez définir n'importe quelle fonction avec le nom de votre choix avant d'appeler la fonction copytree. Cette fonction (qui pourrait également être une expression lambda) prend deux arguments: un nom de répertoire et les fichiers qu'il contient, elle doit retourner un itérable d'ignorer les fichiers.
Cyrille Pontvieux
[x for x in lst if x not in excl]cela ne fait pas la même chose que copytree, qui utilise la correspondance de motifs glob. en.wikipedia.org/wiki/Glob_(programming)
Konstantin Schubert
2
C'est bien. L'ignorer n'était pas utilisé correctement dans la réponse ci-dessus.
Keith Holliday
21

Python 3.8 a introduit l' dirs_exist_okargument pour shutil.copytree:

Copiez récursivement une arborescence de répertoires entière enracinée dans src dans un répertoire nommé dst et retournez le répertoire de destination. dirs_exist_ok dicte s'il faut lever une exception au cas où dst ou tout répertoire parent manquant existe déjà.

Par conséquent, avec Python 3.8+, cela devrait fonctionner:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)
Chris
la source
dirs_exist_ok=Falsepar défaut dans copytree, la première tentative de copie n'échouera-t-elle pas?
Jay
1
@Jay, uniquement si le répertoire existe déjà. J'ai laissé de dirs_exist_okcôté le premier appel pour illustrer la différence (et parce que le répertoire n'existe pas encore dans l'exemple de OP), mais bien sûr vous pouvez l'utiliser si vous le souhaitez.
Chris
Merci, si vous ajoutez un commentaire près du premier exemplaire, je pense que ce serait plus clair :)
Jay
7

les documents indiquent explicitement que le répertoire de destination ne doit pas exister :

Le répertoire de destination, nommé par dst, ne doit pas déjà exister; il sera créé ainsi que les répertoires parents manquants.

Je pense que votre meilleur pari est os.walkle deuxième et tous les répertoires, copy2répertoires et fichiers conséquents et faire des copystatrépertoires supplémentaires . Après tout, c'est précisément ce qui copytreefonctionne comme expliqué dans les documents. Ou vous pourriez copyet copystatchaque répertoire / fichier et os.listdirau lieu de os.walk.

SilentGhost
la source
1

Ceci est inspiré de la meilleure réponse originale fournie par atzz, je viens d'ajouter la logique de remplacement de fichier / dossier. Donc, il ne fusionne pas réellement, mais supprime le fichier / dossier existant et copie le nouveau:

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)

Décommentez le rmtree pour en faire une fonction de déplacement.

radtek
la source
0

Voici ma version de la même tâche ::

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
la source
0

Voici une version inspirée de ce fil qui imite de plus près distutils.file_util.copy_file.

updateonlyest un booléen si Vrai, ne copiera que les fichiers dont la date de modification est plus récente que les fichiers existants, dstsauf dans la liste forceupdatequi les copiera malgré tout.

ignoreet forceupdateattendez-vous à des listes de noms de fichiers ou de dossiers / noms de fichiers relatifs à src et acceptez les caractères génériques de style Unix similaires à globou fnmatch.

La fonction renvoie une liste de fichiers copiés (ou serait copiée si elle dryrunétait True).

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
la source
0

La solution précédente a un problème qui srcpeut remplacer dstsans notification ni exception.

J'ajoute une predict_errorméthode pour prévoir les erreurs avant la copie. copytreeprincipalement basé sur la version de Cyrille Pontvieux.

Utiliser predict_errorpour prédire toutes les erreurs au début est préférable, sauf si vous souhaitez voir une exception levée une par une lors de l'exécution copytreejusqu'à ce que toutes les erreurs soient corrigées.

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)
Mithril
la source
0

Voici ma passe au problème. J'ai modifié le code source de copytree pour conserver la fonctionnalité d'origine, mais maintenant aucune erreur ne se produit lorsque le répertoire existe déjà. Je l'ai également changé afin qu'il n'écrase pas les fichiers existants mais conserve plutôt les deux copies, une avec un nom modifié, car cela était important pour mon application.

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
la source
0

Essaye ça:

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
la source
0

Voici une version qui attend une pathlib.Pathentrée.

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

Notez que cette fonction nécessite Python 3.6, qui est la première version de Python qui os.listdir()prend en charge les objets de type chemin en entrée. Si vous devez prendre en charge des versions antérieures de Python, vous pouvez les remplacer listdir(src)par listdir(str(src)).

Boris Dalstein
la source
-2

je suppose que le moyen le plus rapide et le plus simple serait que python appelle les commandes système ...

exemple..

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

Tar et gzip le répertoire .... décompressez et décompressez le répertoire à l'endroit souhaité.

Ouais?

Kirby
la source
si vous utilisez Windows ... téléchargez 7zip .. et utilisez la ligne de commande pour cela. ... encore des suggestions.
Kirby
31
Les commandes système doivent toujours être un dernier recours. Il est toujours préférable d'utiliser la bibliothèque standard autant que possible afin que votre code soit portable.
jathanism