Bagaimana cara menyalin seluruh direktori file ke direktori yang ada menggunakan Python?

210

Jalankan kode berikut dari direktori yang berisi direktori bernama bar(mengandung satu atau lebih file) dan direktori bernama baz(juga mengandung satu atau lebih file). Pastikan tidak ada direktori yang bernama foo.

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

Itu akan gagal dengan:

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

Saya ingin ini bekerja dengan cara yang sama seperti jika saya telah mengetik:

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

Apakah saya perlu menggunakan shutil.copy()untuk menyalin setiap file bazke dalam foo? (Setelah saya sudah menyalin isi 'bar' ke 'foo' dengan shutil.copytree()?) Atau apakah ada cara yang lebih mudah / lebih baik?

Daryl Spitzer
sumber
1
FYI: di sini adalah fungsi copytree asli, cukup salin dan tempel :)
schlamar
3
Ada masalah Python tentang mengubah shutil.copytree()perilaku untuk memungkinkan penulisan ke direktori yang ada, tetapi ada beberapa detail perilaku yang perlu disepakati.
Nick Chammas
2
Hanya mencatat bahwa permintaan penyempurnaan yang disebutkan di atas telah diterapkan untuk Python 3.8: docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan

Jawaban:

174

Batasan standar ini shutil.copytreetampaknya sewenang-wenang dan menjengkelkan. Penanganan masalah:

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)

Perhatikan bahwa itu tidak sepenuhnya konsisten dengan standar copytree:

  • itu tidak menghormati symlinksdan ignoreparameter untuk direktori root dari srcpohon;
  • itu tidak meningkatkan shutil.Errorkesalahan di tingkat root src;
  • jika terjadi kesalahan saat menyalin subtree, subtree itu akan meningkat shutil.Erroralih-alih mencoba menyalin subtree lain dan menaikkan satu kombinasi shutil.Error.
atzz
sumber
50
Terima kasih! Setuju bahwa ini tampaknya sepenuhnya sewenang-wenang! shutil.copytreetidak os.makedirs(dst)di awal. Tidak ada bagian dari kode yang benar-benar memiliki masalah dengan direktori yang sudah ada sebelumnya. Ini perlu diubah. Setidaknya berikan exist_ok=Falseparameter untuk panggilan
cfi
6
Ini adalah jawaban yang bagus - namun jawaban Mital Vora di bawah ini layak untuk dilihat juga. Mereka menyebut copytree secara rekursif daripada memanggil shutil.copytree () karena masalah yang sama akan muncul sebaliknya. Mungkin mempertimbangkan menggabungkan jawaban atau memperbarui ke Mital Vora.
PJeffes
4
Ini gagal jika diberi jalur yang menyertakan direktori yang tidak kosong di tujuan. Mungkin seseorang bisa menyelesaikan ini dengan rekursi ekor tapi ini modifikasi kode Anda yang berfungsidef 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, sangat menjengkelkan. Ini 4 tahun kemudian, dan shutil.copytree masih memiliki batasan konyol ini. :-(
antred
5
@antred ... tetapi distutils.dir_util.copy_tree(), yang juga berada di stdlib, tidak memiliki batasan seperti itu dan benar-benar berlaku seperti yang diharapkan. Karena itu, tidak ada alasan kuat untuk mencoba membuka gulungan implementasi Anda ( ... biasanya rusak ). Brendan Abel 's jawabannya harus benar-benar menjadi solusi yang diterima sekarang.
Cecil Curry
257

Inilah solusi yang merupakan bagian dari pustaka standar:

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

Lihat pertanyaan serupa ini.

Salin konten direktori ke direktori dengan python

Brendan Abel
sumber
5
Ini bagus karena menggunakan pustaka standar. Simbol, mode, dan waktu juga dapat dipertahankan.
itsafire
1
Melihat kerugian kecil. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string, yaitu tidak menerima PosixPath. Perlu untuk str(PosixPath). Daftar keinginan untuk perbaikan. Selain masalah ini, saya lebih suka jawaban ini.
Sun Bear
@ SunBear, Ya, saya pikir itu akan menjadi kasus dengan sebagian besar perpustakaan lain yang mengambil jalur sebagai string. Bagian dari kerugian untuk memilih untuk tidak membuat Pathobjek mewarisi dari strsaya kira, seperti kebanyakan implementasi sebelumnya dari objek path berorientasi objek ..
Brendan Abel
Btw, saya menemukan kekurangan fungsi ini. Sudah didokumentasikan di sini . Pengguna fungsi ini disarankan untuk menyadarinya.
Sun Bear
1
Ketika "secara teknis bersifat publik", harap perhatikan bahwa pengembang distutils menjelaskannya (tautan yang sama dengan @ SunBear's, thx!) Yang distutils.dir_util.copy_tree()dianggap sebagai detail implementasi distutils dan tidak direkomendasikan untuk penggunaan umum. Solusi nyata harus untuk shutil.copytree()ditingkatkan / diperluas untuk berperilaku lebih seperti distutils.dir_util.copy_tree(), tetapi tanpa kekurangannya. Sementara itu, saya akan terus menggunakan fungsi pembantu kustom mirip dengan beberapa yang disediakan dalam jawaban lain.
Boris Dalstein
61

Dalam sedikit perbaikan pada jawaban atzz untuk fungsi di mana fungsi di atas selalu mencoba untuk menyalin file dari sumber ke tujuan.

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)

Dalam implementasi saya di atas

  • Membuat direktori keluaran jika belum ada
  • Melakukan direktori salin dengan secara berulang memanggil metode saya sendiri.
  • Ketika kita benar-benar menyalin file, saya memeriksa apakah file diubah maka hanya kita yang harus menyalin.

Saya menggunakan fungsi di atas bersama dengan membangun scon. Ini banyak membantu saya karena setiap kali saya mengkompilasi saya mungkin tidak perlu menyalin seluruh set file .. tetapi hanya file yang dimodifikasi.

Mital Vora
sumber
4
Bagus, kecuali bahwa Anda memiliki symlink dan abaikan sebagai argumen, tetapi diabaikan.
Matthew Alpert
Perlu dicatat bahwa st_mtime granularity dapat sama kasarnya dengan 2 detik pada sistem file FAT docs.python.org/2/library/os.html . Menggunakan kode ini dalam konteks di mana pembaruan terjadi secara berurutan, Anda mungkin menemukan penggantian tidak terjadi.
dgh
Ada bug di baris kedua hingga terakhir, seharusnya: if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec
34

Penggabungan yang terinspirasi oleh atzz dan 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)
  • Perilaku yang sama seperti shutil.copytree , dengan symlink dan abaikan parameter
  • Buat struktur tujuan direktori jika tidak ada
  • Tidak akan gagal jika dst sudah ada
Cyrille Pontvieux
sumber
Ini jauh lebih cepat daripada solusi asli ketika sarang direktori dalam. Terima kasih
Kashif
Apakah Anda mendefinisikan fungsi juga bernama 'abaikan' dalam kode di tempat lain?
KenV99
Anda dapat menentukan fungsi apa pun dengan nama apa pun yang Anda suka sebelum memanggil fungsi copytree. Fungsi ini (yang bisa juga merupakan ekspresi lambda) membutuhkan dua argumen: nama direktori dan file di dalamnya, ia harus mengembalikan iterable dari mengabaikan file.
Cyrille Pontvieux
[x for x in lst if x not in excl]ini tidak melakukan hal yang sama dengan copytree, yang menggunakan pencocokan pola glob. en.wikipedia.org/wiki/Glob_(programming)
Konstantin Schubert
2
Ini bagus. Abaikan tidak digunakan dengan benar dalam jawaban di atas.
Keith Holliday
21

Python 3.8 memperkenalkan dirs_exist_okargumen untuk shutil.copytree:

Secara rekursif menyalin seluruh pohon direktori yang di-root di src ke direktori bernama dst dan mengembalikan direktori tujuan. dirs_exist_ok menentukan apakah akan menaikkan pengecualian jika dst atau direktori induk yang hilang sudah ada.

Oleh karena itu, dengan Python 3.8+ ini harus berfungsi:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)
Chris
sumber
dirs_exist_ok=Falsesecara default di copytree, tidak akankah upaya penyalinan pertama gagal?
Jay
1
@ Jay, hanya jika direktori sudah ada. Saya meninggalkan dirs_exist_okpanggilan pertama untuk menggambarkan perbedaan (dan karena direktori belum ada dalam contoh OP), tetapi tentu saja Anda dapat menggunakannya jika Anda mau.
Chris
Terima kasih, jika Anda menambahkan komentar di dekat salinan pertama, saya pikir ini akan membuatnya lebih jelas :)
Jay
7

dokumen secara eksplisit menyatakan bahwa direktori tujuan tidak boleh ada :

Direktori tujuan, dinamai oleh dst, harus belum ada; itu akan dibuat serta direktori induk yang hilang.

Saya pikir taruhan terbaik Anda adalah os.walkdirektori kedua, semua direktori, copy2dan file dan melakukan tambahan copystatuntuk direktori. Lagi pula itulah yang copytreedilakukan seperti yang dijelaskan dalam dokumen. Atau Anda bisa copydan copystatsetiap direktori / file dan os.listdirbukannya os.walk.

SilentGhost
sumber
1

Ini terinspirasi dari jawaban terbaik asli yang disediakan oleh atzz, saya baru saja menambahkan ganti logika file / folder. Jadi itu tidak benar-benar bergabung, tetapi menghapus file / folder yang ada dan menyalin yang baru:

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)

Batalkan komentar rmtree untuk menjadikannya fungsi pemindahan.

radtek
sumber
0

Ini adalah versi saya dari tugas yang sama ::

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
sumber
0

Berikut adalah versi yang terinspirasi oleh utas ini yang lebih mirip meniru distutils.file_util.copy_file.

updateonlyadalah bool jika True, hanya akan menyalin file dengan tanggal modifikasi yang lebih baru dari file yang ada dstkecuali terdaftar di forceupdatemana akan menyalin.

ignoredan forceupdatemengharapkan daftar nama file atau folder / nama file relatif terhadap src dan menerima wildcard gaya Unix mirip dengan globatau fnmatch.

Fungsi mengembalikan daftar file yang disalin (atau akan disalin jika dryrunjika Benar).

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
sumber
0

Solusi sebelumnya memiliki beberapa masalah yang srcdapat ditimpadst tanpa pemberitahuan atau pengecualian.

Saya menambahkan predict_errormetode untuk memprediksi kesalahan sebelum menyalin. copytreeterutama didasarkan pada versi Cyrille Pontvieux.

Menggunakan predict_erroruntuk memprediksi semua kesalahan pada awalnya adalah yang terbaik, kecuali jika Anda ingin melihat pengecualian dimunculkan satu demi satu saat dijalankan copytreehingga memperbaiki semua kesalahan.

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
sumber
0

Ini kartu saya di masalahnya. Saya memodifikasi kode sumber untuk copytree untuk menjaga fungsi asli, tetapi sekarang tidak ada kesalahan terjadi ketika direktori sudah ada. Saya juga mengubahnya sehingga tidak menimpa file yang sudah ada tetapi menyimpan kedua salinan, satu dengan nama yang dimodifikasi, karena ini penting untuk aplikasi saya.

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
sumber
0

Coba ini:

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
sumber
0

Ini adalah versi yang mengharapkan pathlib.Pathinput sebagai.

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

Perhatikan bahwa fungsi ini memerlukan Python 3.6, yang merupakan versi Python pertama di mana os.listdir()mendukung objek path-like sebagai input. Jika Anda perlu mendukung versi Python sebelumnya, Anda bisa menggantinya listdir(src)dengan listdir(str(src)).

Boris Dalstein
sumber
-2

saya akan menganggap cara tercepat dan termudah adalah memanggil python perintah sistem ...

contoh..

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

Tar dan gzip direktori .... unzip dan untar direktori di tempat yang diinginkan.

yah

Kirby
sumber
jika Anda menjalankan di windows ... unduh 7zip .. dan gunakan baris perintah untuk itu. ... lagi hanya saran.
Kirby
31
Perintah sistem harus selalu menjadi pilihan terakhir. Itu selalu lebih baik untuk memanfaatkan perpustakaan standar bila memungkinkan sehingga kode Anda portabel.
jathanism