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

StackOverflow https://stackoverflow.com/questions/1868714

  •  18-09-2019
  •  | 
  •  

Domanda

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).Assicurati che non ci 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/

Ho bisogno di usare shutil.copy() per copiare ciascun file baz in foo?(Dopo aver già copiato il contenuto di 'bar' in 'foo' con shutil.copytree()?) Oppure esiste un modo più semplice/migliore?

È stato utile?

Soluzione

Questa limitazione del shutil.copytree norma sembra arbitraria e fastidioso. Soluzione:

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 la norma copytree:

  • non fa onore parametri symlinks e ignore per la directory radice dell'albero src;
  • essa non solleva shutil.Error per errori a livello radice src;
  • nel caso di errori durante la copia di una sottostruttura, intende sollevare shutil.Error per quella sottostruttura invece di cercare di copiare gli altri sottostrutture e alzando singola shutil.Error combinato.

Altri suggerimenti

Ecco una soluzione che fa parte della libreria standard.

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

Vedere questa domanda simile.

Copiare il contenuto della directory in una directory con Python

In leggero miglioramento sulla risposta atzz alla funzione in cui la funzione di cui sopra cerca sempre di copiare i file dalla sorgente 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)

Nel mio sopra implementazione

  • Creare la directory di output, se non esiste già
  • Fare la directory della copia ricorsivamente chiamando il mio metodo.
  • Quando arriviamo in realtà la copia del file a verificare se il file viene modificato allora solo dovremmo copiare.

Sto usando sopra la funzione insieme a scons costruire. Mi ha aiutato molto come ogni volta quando compilo io non bisogno di copiare l'intero insieme di file .. ma solo i file che sono modificati.

Una fusione è ispirata ad 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 come shutil.copytree , con link simbolici e ignorare parametri
  • Crea struttura di directory di destinazione se inesistente
  • non mancherà se dst esiste già

documenti affermano esplicitamente che directory di destinazione dovrebbe non esistere :

  

La directory di destinazione, nominato dal dst, non deve essere già esistente; verrà creato così come manca directory superiori.

Credo che la soluzione migliore è quella di os.walk il secondo e tutti i conseguenti directory, copy2 directory e file e fanno copystat aggiuntivo per le directory. Dopo tutto quello che è esattamente ciò che copytree fa come spiegato nella documentazione. Oppure si potrebbe copy e copystat ogni directory / file e os.listdir invece di os.walk.

È possibile modificare shutil e ottenere l'effetto (sulla mia versione di shutil questo è on line 315)

Cambia

os.makedirs(dst)

Per

os.makedirs(dst,exist_ok=True)

Questo è ispirato alla migliore risposta originale fornita da atzz, ho appena aggiunto la logica di sostituzione di file/cartella.Quindi in realtà non si unisce, 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)

Decommentare l'rmtree per renderlo una funzione di spostamento.

Vorrei assumere modo più veloce e più semplice sarebbe avere python chiamare i comandi del sistema ...

esempio ..

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

tar e gzip la directory .... decomprimere ed estrarre la directory nel punto prescelto.

yah?

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)

Ecco una versione ispirata a questa discussione che imita più da vicino distutils.file_util.copy_file.

updateonly è un bool se vero, servirà solo a copiare i file con date modificati più recenti rispetto ai file in dst esistente a meno che non elencati in forceupdate che copierà indipendentemente.

ignore e forceupdate aspettano liste di nomi di file o cartelle / nomi di file relativi a src ed accettare Unix-style caratteri jolly simili a glob o fnmatch.

La funzione restituisce un elenco di file copiato (o sarebbe copiato se dryrun se 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

La soluzione precedente ha qualche problema che src può sovrascrivere dst senza alcuna notifica o eccezioni.

aggiungo un metodo per predire predict_error errori prima copy.copytree principalmente base su versione Cyrille Pontvieux.

Utilizzando predict_error di prevedere tutti gli errori in un primo momento è la migliore, a meno che non ti piace vedere eccezione sollevata uno per l'altro quando eseguire copytree fino correggere tutti gli errori.

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)

Ecco il mio passaggio al problema. Ho modificato il codice sorgente per copytree per mantenere la funzionalità originale, ma ora nessun errore si verifica quando la directory esiste già. Ho anche cambiato in modo da non sovrascrivere i file esistenti, ma piuttosto mantiene entrambe le copie di cui una con un nome modificato, dal momento che questo è stato 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)

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")
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top