Come copio un'intera directory di file in una directory esistente usando Python?


210

Eseguire il codice seguente da una directory che contiene una directory denominata bar(contenente uno o più file) e una directory denominata baz(contenente anche uno o più file). Assicurarsi che non vi sia una directory denominata foo.

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

Fallirà con:

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

Voglio che funzioni allo stesso modo come se avessi digitato:

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

Devo usare shutil.copy()per copiare ogni file bazin foo? (Dopo aver già copiato il contenuto di 'bar' in 'pippo' shutil.copytree()?) O c'è un modo più semplice / migliore?


1
Cordiali saluti: ecco la funzione originale copytree, basta copiarla e
patchala

3
Esiste un problema di Python sulla modifica shutil.copytree()del comportamento per consentire la scrittura in una directory esistente, ma ci sono alcuni dettagli sul comportamento che devono essere concordati.
Nick Chammas,

2
Solo notando che la richiesta di miglioramento sopra menzionata è stata implementata per Python 3.8: docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan,

Risposte:


174

Questa limitazione dello standard shutil.copytreesembra arbitraria e fastidiosa. Soluzione:

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)

Si noti che non è del tutto coerente con lo standard copytree:

  • non onora symlinkse ignoreparametri per la directory radice srcdell'albero;
  • non genera shutil.Errorerrori a livello di root di src;
  • in caso di errori durante la copia di una sottostruttura, aumenterà shutil.Errorper quella sottostruttura invece di provare a copiare altri sottotitoli e alzare il singolo combinato shutil.Error.

50
Grazie! Concordo sul fatto che questo sembra del tutto arbitrario! shutil.copytreefa un os.makedirs(dst)all'inizio. Nessuna parte del codice avrebbe effettivamente un problema con una directory preesistente. Questo deve essere cambiato. Almeno fornire un exist_ok=Falseparametro alla chiamata
cfi

6
Questa è una buona risposta - tuttavia vale la pena dare un'occhiata anche alla risposta di seguito di Mital Vora. Hanno chiamato ricorsivamente copytree anziché chiamare shutil.copytree () perché lo stesso problema sorgerà altrimenti. Forse prendere in considerazione la fusione di risposte o l'aggiornamento a Mital Vora.
PJeffes,

4
Ciò non riesce se viene fornito un percorso che include una directory che non è vuota nella destinazione. Forse qualcuno potrebbe risolverlo con la ricorsione della coda ma ecco una modifica al tuo codice che funzionadef 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 fastidioso. Sono passati 4 anni e shutil.copytree ha ancora questa sciocca restrizione. :-(
antred

5
@antred ... ma distutils.dir_util.copy_tree(), che risiede anche nello stdlib, non ha tali restrizioni e si comporta effettivamente come previsto. Detto questo, non c'è motivo convincente per tentare di srotolare la propria ( ... in genere interrotta ) implementazione. La risposta di Brendan Abel dovrebbe essere assolutamente la soluzione accettata ora.
Cecil Curry,

257

Ecco una soluzione che fa parte della libreria standard:

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

Vedi questa domanda simile.

Copia il contenuto della directory in una directory con Python


5
Questo è buono perché utilizza la libreria standard. È possibile conservare anche collegamenti simbolici, modalità e ora.
itsafire

1
Ho notato un piccolo svantaggio. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string, cioè non accetta PosixPath. Necessario str(PosixPath). Lista dei desideri da migliorare. A parte questo, preferisco questa risposta.
Sun Bear,

@SunBear, Sì, penso che sarà il caso della maggior parte delle altre librerie che prendono percorsi come stringhe. Parte del rovescio della medaglia nella scelta di non far Pathereditare l' oggetto da str, suppongo, come la maggior parte delle precedenti implementazioni di oggetti orientati agli oggetti ..
Brendan Abel,

A proposito, ho riscontrato una carenza documentata di questa funzione. È documentato qui . Agli utenti di questa funzione è stato consigliato di esserne consapevoli.
Sun Bear,

1
Sebbene sia "tecnicamente pubblico", tieni presente che gli sviluppatori di distutils hanno chiarito (lo stesso link di @ SunBear's, grazie!) Che distutils.dir_util.copy_tree()è considerato un dettaglio di implementazione di distutils e non è raccomandato per l'uso pubblico. La vera soluzione dovrebbe essere quella shutil.copytree()di essere migliorata / estesa per comportarsi più come distutils.dir_util.copy_tree(), ma senza i suoi difetti. Nel frattempo, continuerò a utilizzare funzioni di supporto personalizzate simili a quelle fornite in altre risposte.
Boris Dalstein,

61

In lieve miglioramento sulla risposta di atzz alla funzione in cui la funzione precedente cerca sempre di copiare i file dall'origine alla destinazione.

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)

Nella mia implementazione sopra

  • Creazione della directory di output se non esiste già
  • Fare la directory di copia chiamando ricorsivamente il mio metodo.
  • Quando arriviamo a copiare effettivamente il file, controllo se il file viene modificato, quindi dovremmo solo copiarlo.

Sto usando la funzione sopra insieme a scons build. Mi ha aiutato moltissimo come ogni volta che compilo, potrebbe non essere necessario copiare l'intero set di file .. ma solo i file che vengono modificati.


4
Bello, tranne che hai collegamenti simbolici e ignori come argomenti, ma vengono ignorati.
Matthew Alpert,

Vale la pena notare che la granularità di st_mtime può essere approssimativa di 2 secondi sui filesystem FAT docs.python.org/2/library/os.html . Utilizzando questo codice in un contesto in cui gli aggiornamenti avvengono in rapida successione, è possibile che non si verifichino sostituzioni.
dgh

C'è un bug nella penultima riga, dovrebbe essere: if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec,

34

Un mix ispirato da atzz e 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)
  • Stesso comportamento di shutil.copytree , con collegamenti simbolici e parametri ignorati
  • Creare la struttura di destinazione della directory se inesistente
  • Non fallirà se dst esiste già

Questo è molto più veloce della soluzione originale quando l'annidamento della directory è profondo. Grazie
Kashif

Hai definito una funzione chiamata anche "ignora" nel codice altrove?
KenV99,

È possibile definire qualsiasi funzione con qualsiasi nome che ti piace prima di chiamare la funzione copytree. Questa funzione (che potrebbe anche essere un'espressione lambda) accetta due argomenti: un nome di directory e i file in esso contenuti, dovrebbe restituire un iterabile di file ignorati.
Cyrille Pontvieux,

[x for x in lst if x not in excl]questo non fa lo stesso di copytree, che usa la corrispondenza del modello glob. en.wikipedia.org/wiki/Glob_(programmazione)
Konstantin Schubert

2
Questo è fantastico L'ignore non veniva utilizzato correttamente nella risposta sopra.
Keith Holliday,

21

Python 3.8 ha introdotto l' dirs_exist_okargomento a shutil.copytree:

Copia ricorsivamente un intero albero di directory radicato su src in una directory denominata dst e restituisce la directory di destinazione. dirs_exist_ok determina se sollevare un'eccezione nel caso in cui dst o una directory padre mancante esistano già.

Pertanto, con Python 3.8+ dovrebbe funzionare:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)

dirs_exist_ok=Falsedi default in copytree, il primo tentativo di copia non fallirà?
Jay,

1
@Jay, solo se la directory esiste già. Ho lasciato dirs_exist_okfuori la prima chiamata per illustrare la differenza (e perché la directory non esiste ancora nell'esempio di OP), ma ovviamente puoi usarla se vuoi.
Chris,

Grazie, se aggiungi un commento vicino alla prima copia, penso che lo renderebbe più chiaro :)
Jay,

7

i documenti dichiarano esplicitamente che la directory di destinazione non dovrebbe esistere :

La directory di destinazione, nominata da dst, non deve già esistere; verrà creato così come le directory padre mancanti.

Penso che la tua scommessa migliore sia os.walkla seconda e tutte le conseguenti directory, copy2directory e file e fare ulteriori copystatper le directory. Dopo tutto questo è esattamente ciò che copytreefa come spiegato nei documenti. Oppure potresti copye copystatogni directory / file e os.listdirinvece di os.walk.


1

Questo si ispira alla migliore risposta originale fornita da atzz, ho appena aggiunto la logica di sostituzione di file / cartelle. Quindi non si fonde effettivamente, ma elimina il file / cartella esistente e copia quello nuovo:

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)

Rimuovi il commento da rmtree per renderlo una funzione di spostamento.


0

Ecco la mia versione dello stesso compito:

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

Ecco una versione ispirata a questo thread che imita più da vicino distutils.file_util.copy_file .

updateonlyè un bool se True, copierà solo i file con date modificate più recenti dei file esistenti a dstmeno che non siano elencati inforceupdate che cui verranno copiati indipendentemente.

ignoree forceupdateaspettarsi elenchi di nomi di file o cartelle / nomi di file relativi src e accettare caratteri jolly in stile Unix simili a globofnmatch .

La funzione restituisce un elenco di file copiati (o verrebbe copiato se dryrunimpostato su 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

La soluzione precedente presenta alcuni problemi che srcpotrebbero essere sovrascrittidst senza alcuna notifica o eccezione.

Aggiungo un predict_errormetodo per prevedere gli errori prima della copia. copytreeprincipalmente basato sulla versione di Cyrille Pontvieux.

Utilizzando predict_errorprevedere tutti gli errori in un primo momento è la cosa migliore, a meno che non ti piace vedere eccezione sollevata uno per l'altro quando l'esecuzione copytreefino a quando tutti gli errori correzione.

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

Ecco il mio passaggio al problema. Ho modificato il codice sorgente per copytree per mantenere la funzionalità originale, ma ora non si verifica alcun errore quando la directory esiste già. L'ho anche cambiato in modo che non sovrascriva i file esistenti ma conserva entrambe le copie, una con un nome modificato, poiché questo era importante per la mia applicazione.

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

Prova questo:

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

Ecco una versione che prevede un pathlib.Pathinput.

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

Nota che questa funzione richiede Python 3.6, che è la prima versione di Python in cui os.listdir()supporta oggetti simili a percorsi come input. Se devi supportare versioni precedenti di Python, puoi sostituirlo listdir(src)con listdir(str(src)).


-2

suppongo che il modo più veloce e più semplice sarebbe che Python chiami i comandi di sistema ...

esempio..

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

Tar e gzip su la directory .... decomprimere e decomprimere la directory nella posizione desiderata.

yah?


se stai eseguendo Windows ... scarica 7zip .. e usa la riga di comando per questo. ... ancora solo suggerimenti.
Kirby,

31
I comandi di sistema dovrebbero sempre essere l'ultima risorsa. È sempre meglio utilizzare la libreria standard ogni volta che è possibile in modo che il tuo codice sia portatile.
jathanism
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.