web-dev-qa-db-fra.com

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

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 de baz dans foo? (Après avoir déjà copié le contenu de 'bar' dans 'foo' avec shutil.copytree()?? Ou existe-t-il un moyen plus simple/meilleur?

134
Daryl Spitzer

Cette limitation du shutil.copytree standard semble arbitraire et gênante. Solution de contournement:

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 que ce n'est pas tout à fait compatible avec le copytree standard:

  • il n'honore pas les paramètres symlinks et ignore du répertoire racine de l'arborescence src;
  • il ne soulève pas shutil.Error pour les erreurs au niveau racine src;
  • en cas d'erreur lors de la copie d'un sous-arbre, il lèvera shutil.Error pour ce sous-arbre au lieu d'essayer de copier d'autres sous-arbres et de lever un seul shutil.Error combiné.
124
atzz

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.

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

135
Brendan Abel

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 à 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 de façon récursive ma propre méthode.
  • Quand nous en venons à copier le fichier, je vérifie si le fichier est modifié, alors nous ne devrions copier que

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

50
Mital Vora

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)
  • Même comportement que shutil.copytree, avec liens symboliques et ignore parameters
  • Créer une structure de destination de répertoire si non existante
  • N'échouera pas si dst existe déjà
29
Cyrille Pontvieux

docs indique explicitement que le répertoire de destination doit être non 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 choix est de os.walk le deuxième répertoire et tous les répertoires conséquents, copy2 répertoire et fichiers, et de faire copystat supplémentaire pour les répertoires. Après tout, c’est précisément ce que copytree fait, comme expliqué dans la documentation. Ou vous pourriez copy et copystat chaque répertoire/fichier et os.listdir au lieu de os.walk.

7
SilentGhost

Vous pouvez modifier shutil et obtenir l'effet (sur ma version de shutil ceci est en ligne 315)

Changement

os.makedirs(dst)

À

os.makedirs(dst,exist_ok=True)
3
user2728397

je supposerais que le moyen le plus rapide et le plus simple serait d’avoir python d’appeler 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é. 

yah?

1
Kirby

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, il 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 l'arborescence pour en faire une fonction de déplacement.

1
radtek

Voici ma passe au problème. J'ai modifié le code source de copytree pour conserver la fonctionnalité d'origine, mais aucune erreur ne se produit lorsque le répertoire existe déjà. Je l'ai également modifié pour ne pas écraser les fichiers existants, mais conserver les deux copies, une avec un nom modifié, car c'é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)
0
James

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

updateonly est un booléen si True, ne copiera que les fichiers dont la date de modification est plus récente que les fichiers existants dans dst, à moins qu'ils ne soient répertoriés dans forceupdate, qui seront copiés malgré tout.

ignore et forceupdate attendent des listes de noms de fichiers ou de dossiers/noms de fichiers relatifs à src et acceptent les caractères génériques de type Unix similaires à glob ou fnmatch.

La fonction renvoie une liste de fichiers copiés (ou seraient copiés si dryrun si 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
0
KenV99

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

La solution précédente présente un problème: src peut écraser dst sans notification ni exception.

J'ajoute une méthode predict_error pour prédire les erreurs avant la copie .copytree principalement basée sur la version de Cyrille Pontvieux.

Il est préférable d'utiliser predict_error pour prédire toutes les erreurs au début, à moins que vous ne souhaitiez voir l'exception levée l'une après l'autre lors de l'exécution de copytree jusqu'à correction de toutes les erreurs.

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

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")
0
J0KER11