Jak skopiować cały katalog plików do istniejącego katalogu za pomocą Pythona?

210

Uruchom następujący kod z katalogu zawierającego katalog o nazwie bar(zawierający jeden lub więcej plików) i katalog o nazwie baz(zawierający także jeden lub więcej plików). Upewnij się, że nie ma katalogu o nazwie foo.

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

Nie powiedzie się z:

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

Chcę, aby działało to tak samo, jak gdybym wpisał:

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

Czy muszę korzystać shutil.copy()skopiować każdy plik w bazpod foo? (Po tym, jak już skopiowałem zawartość „paska” do „foo” za pomocą shutil.copytree()?) Czy istnieje łatwiejszy / lepszy sposób?

Daryl Spitzer
źródło
1
FYI: tutaj jest oryginalna funkcja copytree, po prostu skopiuj ją i
załataj
3
Istnieje problem z Pythonem dotyczący zmiany shutil.copytree()zachowania, aby umożliwić zapis do istniejącego katalogu, ale istnieją pewne szczegóły zachowania, które należy uzgodnić.
Nick Chammas,
2
Wystarczy zauważyć, że wspomniane wyżej żądanie rozszerzenia zostało zaimplementowane dla Python 3.8: docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan

Odpowiedzi:

174

To ograniczenie standardu shutil.copytreewydaje się arbitralne i irytujące. Obejście:

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)

Pamiętaj, że nie jest to w pełni zgodne ze standardem copytree:

  • nie honoruje symlinksi nie ignoreokreśla parametrów katalogu głównego srcdrzewa;
  • nie podnosi shutil.Errorbłędów na poziomie głównym src;
  • w przypadku błędów podczas kopiowania poddrzewa, będzie ono podnosić shutil.Errordla tego poddrzewa, zamiast próbować kopiować inne poddrzewa i podbijać pojedyncze shutil.Error.
atzz
źródło
50
Dzięki! Zgadzam się, że wydaje się to całkowicie arbitralne! shutil.copytreerobi os.makedirs(dst)na początku. Żadna część kodu nie miałaby problemu z wcześniej istniejącym katalogiem. To trzeba zmienić. Przynajmniej podaj exist_ok=Falseparametr wywołania
cfi
6
To dobra odpowiedź - warto jednak przyjrzeć się również poniższej odpowiedzi Mital Vora. Wywołali rekurencyjnie copytree zamiast wywoływać shutil.copytree (), ponieważ w przeciwnym razie pojawiłby się ten sam problem. Być może rozważ połączenie odpowiedzi lub aktualizację Mital Vora.
PJeffes
4
Nie powiedzie się to, jeśli podano ścieżkę zawierającą katalog, który nie jest pusty w miejscu docelowym. Może ktoś może rozwiązać ten problem za pomocą rekurencji ogona, ale oto modyfikacja kodu, który działadef 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, bardzo denerwujące. To 4 lata później, a shutil.copytree wciąż ma to głupie ograniczenie. :-(
antred
5
@antred ... ale distutils.dir_util.copy_tree(), który również znajduje się w stdlib, nie ma takich ograniczeń i faktycznie zachowuje się zgodnie z oczekiwaniami. Biorąc to pod uwagę, nie ma istotnego powodu, aby spróbować rozwinąć własną ( ... zwykle zepsutą ) implementację. Brendan Abel „s odpowiedź powinna być absolutnie przyjętego rozwiązania teraz.
Cecil Curry
257

Oto rozwiązanie, które jest częścią standardowej biblioteki:

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

Zobacz to podobne pytanie.

Skopiuj zawartość katalogu do katalogu za pomocą Pythona

Brendan Abel
źródło
5
To jest dobre, ponieważ używa standardowej biblioteki. Można również zachować dowiązania symboliczne, tryb i czas.
itsafire
1
Zauważyłem niewielką wadę. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string, tzn. nie akceptuje PosixPath. Potrzebujesz str(PosixPath). Lista życzeń do poprawy. Poza tym wolę tę odpowiedź.
Sun Bear
@SunBear, tak, myślę, że tak będzie w przypadku większości innych bibliotek, które przyjmują ścieżki jako ciągi znaków. Część wady wyboru, aby nie Pathdziedziczyć obiektustrPrzypuszczam, że jest , podobnie jak większość wcześniejszych implementacji obiektowych ścieżek obiektowych.
Brendan Abel
Przy okazji natknąłem się na udokumentowany brak tej funkcji. Jest to udokumentowane tutaj . Użytkownicy tej funkcji powinni być tego świadomi.
Sun Bear
1
Chociaż jest „technicznie publiczny”, należy pamiętać, że twórcy distutils wyraźnie (ten sam link, co @ SunBear's, thx!) distutils.dir_util.copy_tree()Jest uważany za szczegół implementacji distutils i nie jest zalecany do użytku publicznego. Prawdziwe rozwiązanie powinno zostać shutil.copytree()ulepszone / rozszerzone, aby zachowywało się bardziej podobnie distutils.dir_util.copy_tree(), ale bez jego wad. W międzyczasie będę nadal używać niestandardowych funkcji pomocniczych podobnych do niektórych z innych odpowiedzi.
Boris Dalstein,
61

W niewielkim stopniu poprawiono odpowiedź atzz na funkcję, w której powyższa funkcja zawsze próbuje skopiować pliki ze źródła do miejsca docelowego.

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)

W mojej powyższej realizacji

  • Tworzenie katalogu wyjściowego, jeśli jeszcze nie istnieje
  • Robienie katalogu kopiowania poprzez rekurencyjne wywoływanie mojej własnej metody.
  • Kiedy dochodzimy do faktycznego skopiowania pliku, sprawdzam, czy plik został zmodyfikowany, tylko my powinniśmy skopiować.

Używam powyższej funkcji wraz z kompilacją scons. Bardzo mi to pomogło, ponieważ za każdym razem, gdy kompiluję, może nie być konieczne kopiowanie całego zestawu plików, a jedynie zmodyfikowane pliki.

Mital Vora
źródło
4
Fajnie, z wyjątkiem tego, że masz dowiązania symboliczne i ignorujesz jako argumenty, ale są one ignorowane.
Matthew Alpert
Warto zauważyć, że ziarnistość st_mtime może być tak gruba jak 2 sekundy w systemach plików FAT docs.python.org/2/library/os.html . Używanie tego kodu w kontekście, w którym aktualizacje następują szybko po sobie, może się okazać, że przesłonięcia nie mają miejsca.
dgh
W przedostatniej linii jest błąd, powinien być: if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec,
34

Scalanie inspirowane przez atzz i 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)
  • Takie samo zachowanie jak shutil.copytree , z dowiązaniami symbolicznymi i parametrami ignorowania
  • Utwórz strukturę docelową katalogu, jeśli nie istnieje
  • Nie zawiedzie, jeśli dst już istnieje
Cyrille Pontvieux
źródło
Jest to o wiele znacznie szybsze niż oryginalne rozwiązanie, gdy zagnieżdżanie katalogów jest głębokie. Dzięki
Kashif,
Czy zdefiniowałeś funkcję o nazwie „ignoruj” w kodzie gdzie indziej?
KenV99
Możesz wywołać dowolną funkcję o dowolnej nazwie przed wywołaniem funkcji copytree. Ta funkcja (która może być również wyrażeniem lambda) wymaga dwóch argumentów: nazwy katalogu i zawartych w niej plików, powinna zwrócić iterowalną ścieżkę ignorowania plików.
Cyrille Pontvieux
[x for x in lst if x not in excl]nie działa to tak samo jak copytree, który wykorzystuje dopasowanie wzorca globu. en.wikipedia.org/wiki/Glob_(programming)
Konstantin Schubert
2
To jest świetne. Ignorowanie nie było poprawnie wykorzystane w powyższej odpowiedzi.
Keith Holliday
21

Python 3.8 wprowadził dirs_exist_okargument do shutil.copytree:

Rekurencyjnie skopiuj całe drzewo katalogów zrootowane w src do katalogu o nazwie dst i zwróć katalog docelowy. dirs_exist_ok określa, czy zgłosić wyjątek w przypadku, gdy dst lub brakujący katalog nadrzędny już istnieje.

Dlatego w przypadku Python 3.8+ powinno to działać:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)
Chris
źródło
dirs_exist_ok=Falsedomyślnie w copytree, czy pierwsza próba kopiowania nie powiedzie się?
Jay
1
@Jay, tylko jeśli katalog już istnieje. Pominąłem dirs_exist_okpierwsze wezwanie, aby zilustrować różnicę (a ponieważ katalog nie istnieje jeszcze w przykładzie OP), ale oczywiście możesz go użyć, jeśli chcesz.
Chris
Dzięki, jeśli dodasz komentarz w pobliżu pierwszej kopii, myślę, że to by było jaśniejsze :)
Jay
7

Dokumenty wyraźnie stwierdzają, że katalog docelowy nie powinien istnieć :

Katalog docelowy, nazwany przez dst, nie może już istnieć; zostanie utworzony, podobnie jak brakujące katalogi nadrzędne.

Myślę, że najlepszym rozwiązaniem jest os.walkdrugi i wszystkie kolejne katalogi, copy2katalogi i pliki oraz dodatkowe copystatkatalogi. W końcu to dokładnie to copytree, co wyjaśniono w dokumentach. Lub możesz copyi copystatkażdy katalog / plik i os.listdirzamiast os.walk.

SilentGhost
źródło
1

Jest to inspirowane oryginalną najlepszą odpowiedzią udzieloną przez atzz, właśnie dodałem logikę zastępowania pliku / folderu. Więc nie łączy się, ale usuwa istniejący plik / folder i kopiuje nowy:

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)

Usuń komentarz z rmtree, aby uczynić go funkcją ruchu.

radtek
źródło
0

Oto moja wersja tego samego zadania:

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
źródło
0

Oto wersja inspirowana tym wątkiem, która bardziej naśladuje distutils.file_util.copy_file.

updateonlyto bool, jeśli Prawda, kopiuje tylko pliki ze zmodyfikowanymi datami nowszymi niż istniejące, dstchyba że podano na liście, forceupdateniezależnie od nich.

ignorei forceupdateoczekuj list nazw plików lub nazw folderów / plików względem src i akceptuj symbole wieloznaczne w stylu uniksowym podobne do globlub fnmatch.

Funkcja zwraca listę skopiowanych plików (lub zostałaby skopiowana, dryrunjeśli ma wartość 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
źródło
0

Poprzednie rozwiązanie ma pewne problemy, które srcmogą zostać zastąpione dstbez powiadomienia lub wyjątku.

Dodaję predict_errormetodę przewidywania błędów przed kopiowaniem. copytreeopiera się głównie na wersji Cyrille Pontvieux.

Korzystanie predict_errorprzewidzieć wszystkie błędy na początku jest najlepszy, chyba że chcesz zobaczyć wyjątek podniesiony jeden przez drugiego, kiedy wykonać copytreeaż wszystkie poprawki błędów.

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
źródło
0

Oto moja przepustka do problemu. Zmodyfikowałem kod źródłowy dla copytree, aby zachować oryginalną funkcjonalność, ale teraz nie występuje błąd, gdy katalog już istnieje. Zmieniłem to również, aby nie zastępowało istniejących plików, ale zachowało obie kopie, jedną o zmodyfikowanej nazwie, ponieważ było to ważne dla mojej aplikacji.

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
źródło
0

Spróbuj tego:

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
źródło
0

Oto wersja, która oczekuje pathlib.Pathjako dane wejściowe.

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

Zauważ, że ta funkcja wymaga Pythona 3.6, który jest pierwszą wersją Pythona, w której jako obiekt os.listdir()obsługuje obiekty podobne do ścieżki. Jeśli trzeba wspierać wcześniejsze wersje Pythona, można zastąpić listdir(src)przez listdir(str(src)).

Boris Dalstein
źródło
-2

Zakładam, że najszybszym i najprostszym sposobem byłoby wywołanie przez Python poleceń systemowych ...

przykład..

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

Tar i gzip w górę katalogu .... rozpakuj i rozpakuj katalog w żądanym miejscu.

tak?

Kirby
źródło
jeśli pracujesz w systemie Windows ... pobierz 7zip .. i użyj do tego wiersza poleceń. ... znowu tylko sugestie.
Kirby,
31
Polecenia systemowe powinny zawsze być ostatecznością. Zawsze lepiej jest używać standardowej biblioteki, gdy tylko jest to możliwe, aby kod był przenośny.
jathanism