Pythonを使用してファイルのディレクトリ全体を既存のディレクトリにコピーするにはどうすればよいですか?


210

bar(1つ以上のファイルを含む)という名前のディレクトリと(1つ以上のファイルを含む)という名前のディレクトリを含むディレクトリから次のコードを実行しますbaz。という名前のディレクトリがないことを確認してくださいfoo

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

それは失敗します:

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

私はこれをタイプしたのと同じように動作させたい:

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

私が使用する必要がありますshutil.copy()内の各ファイルをコピーすることbazfoo?(「bar」の内容をすでに「foo」にコピーした後はshutil.copytree()?)または、より簡単/より良い方法はありますか?


1
FYI:ここでは、元copytree機能はそれをコピーして、パッチを適用、です:)
schlamar

3
既存のディレクトリへの書き込みを許可するようにの動作変更することに関するPythonの問題shutil.copytree()がありますが、合意する必要のある動作の詳細がいくつかあります。
Nick Chammas

2
上記の拡張リクエストがPython 3.8に実装されたことに注意してください:docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan

回答:


174

標準のこの制限はshutil.copytree恣意的で迷惑なようです。回避策:

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)

標準と完全に一致しているわけではないことに注意してくださいcopytree

  • ツリーのルートディレクトリのパラメータsymlinksignoreパラメータは考慮されませんsrc
  • shutil.Errorのルートレベルのエラーに対しては発生しませんsrc
  • サブツリーのコピー中にエラーが発生した場合は、shutil.Error他のサブツリーをコピーして単一の結合されたを発生させる代わりに、そのサブツリーについて発生しますshutil.Error

50
ありがとう!これは完全に恣意的であることに同意します!shutil.copytreeos.makedirs(dst)開始時に。コードのどの部分も、実際には既存のディレクトリに問題があることはありません。これは変更する必要があります。少なくとも提供exist_ok=Falseコールへのパラメータ
CFI

6
これは良い答えですが、以下のMital Voraの答えも一見の価値があります。同じ問題が別の方法で発生するため、shutil.copytree()を呼び出すのではなく、copytreeを再帰的に呼び出しています。回答をマージするか、Mital Voraに更新することを検討してください。
PJeffes 2013年

4
宛先で空ではないディレクトリを含むパスを指定すると、これは失敗します。たぶん誰かが末尾再帰でこれを解決できるかもしれませんが、これが機能するコードへの変更です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): 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
ええ、超迷惑です。それは4年後、shutil.copytreeにはまだこのばかげた制限があります。:-(
2016

5
@antred ... but stdlibにありますがdistutils.dir_util.copy_tree()、そのような制限はなく、実際には期待どおりに動作します。そのため、独自の(通常は壊れている)実装を展開しようとする説得力のある理由はありません。ブレンダン・アベル答えは、今や絶対的に受け入れられる解決策になるはずです。
セシルカレー

257

標準ライブラリの一部であるソリューションは次のとおりです。

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

この同様の質問を参照してください。

Pythonでディレクトリの内容をディレクトリにコピーする


5
標準ライブラリを使用しているため、これは良いものです。シンボリックリンク、モード、時間も保持できます。
itsafire 2016

1
小さな欠点に気づきました。distutils.errors.DistutilsInternalError: mkpath: 'name' must be a stringつまり、受け入れませんPosixPath。する必要がありstr(PosixPath)ます。改善のお願いリスト。この問題以外に、私はこの答えを好みます。
Sun Bear

@SunBear、うん、パスを文字列として受け取る他のほとんどのライブラリでもそうだと思います。オブジェクト指向パスオブジェクトの以前の実装のほとんどと同様に、Pathオブジェクトが継承しないようにすることを選択することのマイナス面の一部str..
Brendan Abel

ところで、私はこの機能の文書化された欠陥に出くわしました。ここに記載されています。この機能のユーザーは、この機能を知っておくべきアドバイスでした。
Sun Bear

1
「技術的にパブリック」ですが、distutilsの開発者は、distutils の実装の詳細と見なされ、パブリックでの使用は推奨されないことを明確にしたことに注意してください(@SunBearのリンクと同じリンク)distutils.dir_util.copy_tree()。本当の解決策は、のshutil.copytree()ように動作するようdistutils.dir_util.copy_tree()に改善または拡張することですが、欠点はありません。それまでの間、他の回答で提供されているものと同様のカスタムヘルパー関数を引き続き使用します。
Boris Dalstein

61

上記の関数が常にソースから宛先にファイルをコピーしようとする関数に対するatzzの回答をわずかに改善しました。

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)

私の上記の実装では

  • まだ存在しない場合の出力ディレクトリの作成
  • 独自のメソッドを再帰的に呼び出してディレクトリをコピーする。
  • 実際にファイルをコピーするときは、ファイルが変更されているかどうかを確認し、コピーするだけです。

私はsconsビルドと共に上記の関数を使用しています。コンパイルするたびに、ファイルのセット全体をコピーする必要はないかもしれませんが、変更されたファイルのみをコピーできるので、とても役に立ちました。


4
いいですが、シンボリックリンクがあり、引数として無視されますが、無視されます。
マシューアルパート2013年

FATファイルシステムdocs.python.org/2/library/os.htmlでは、st_mtimeの粒度が2秒と粗い場合があることに注意してください。更新が連続して行われる状況でこのコードを使用すると、オーバーライドが行われない場合があります。
2014年

最後から2番目の行にはバグがあり、次のようになります if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec

34

atzzと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)
  • shutil.copytreeと同じ動作、シンボリックリンク無視パラメータあり
  • 存在しない場合、ディレクトリ宛先構造を作成します
  • dstがすでに存在する場合は失敗しません

これは、ディレクトリのネストが深い場合、元のソリューションよりもはるかに高速です。ありがとう
Kashif

他のコードで「無視」とも呼ばれる関数を定義しましたか?
KenV99 2016

copytree関数を呼び出す前に、任意の名前で任意の関数を定義できます。この関数(ラムダ式の場合もあります)は2つの引数を取ります。ディレクトリ名とその中のファイルです。無視するファイルの反復可能オブジェクトを返す必要があります。
シリルポンヴィユー

[x for x in lst if x not in excl] これは、globパターンマッチングを使用するcopytreeとは異なります。 )en.wikipedia.org/wiki/Glob_(programming
コンスタンチン・シューベルト

2
これは素晴らしい。上記の回答では、無​​視が正しく使用されていませんでした。
Keith Holliday、2016

21

Python 3.8では、次のdirs_exist_ok引数導入されましshutil.copytree

srcをルートとするディレクトリツリー全体をdstという名前のディレクトリに再帰的にコピーし、宛先ディレクトリを返します。dirs_exist_okはdstまたは不足している親ディレクトリがすでに存在する場合に例外を発生させるかどうかを指定します。

したがって、Python 3.8以降ではこれでうまくいくはずです。

import shutil

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

dirs_exist_ok=Falseデフォルトでcopytreeでは、最初のコピーの試行は失敗しませんか?
ジェイ

1
@Jay、ディレクトリがすでに存在する場合のみ。私は左のdirs_exist_ok違いを説明するために(およびディレクトリがまだOPの例では存在しないので)最初の呼び出しのうち、しかし、あなたがしたい場合は、もちろん、あなたはそれを使用することができます。
クリス

ありがとう、最初のコピーの近くにコメントを追加すると、それがより明確になると思います:)
Jay

7

ドキュメントは、宛先ディレクトリが存在してはならないことを明示的に述べてます

で指定された宛先ディレクトリがdstすでに存在していてはなりません。作成され、欠落している親ディレクトリも作成されます。

あなたの最善の策はos.walk、2番目以降のすべてのディレクトリ、copy2ディレクトリ、およびファイルにあり、ディレクトリに追加copystatすることです。結局のところ、それcopytreeはドキュメントで説明されているとおりです。または、代わりにcopycopystat各ディレクトリ/ファイルをos.listdir使用することもできますos.walk


1

これは、atzzによって提供された元のベストアンサーからインスピレーションを得て、ファイル/フォルダーロジックの置換を追加しました。したがって、実際にはマージされませんが、既存のファイル/フォルダーを削除し、新しいフォルダーをコピーします。

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)

rmtreeのコメントを外して移動関数にします。


0

これは同じタスクの私のバージョンです::

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

これは、このスレッドからインスピレーションを得た、より厳密に模倣したバージョンdistutils.file_util.copy_fileです。

updateonlyTrueの場合はブール値です。dstリストに記載されforceupdateている場合を除いて、既存のファイルよりも変更日が新しいファイルのみがコピーされます。

ignoreそしてforceupdate、ファイル名やフォルダのリストを期待する/ファイル名への相対 srcおよびUnixスタイルはと類似ワイルドカード受け入れますglobfnmatch

この関数は、コピーされたファイルのリストを返します(dryrunTrueの場合はコピーされます)。

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

以前のソリューションには、src上書きされる可能性があるいくつかの問題がありますdst、通知や例外なしにかあります。

predict_errorコピーする前にエラーを予測するメソッドを追加します。copytree主にCyrille Pontvieuxのバージョンに基づいています。

predict_errorすべてのエラーをcopytree修正するまで実行時に例外が発生するのを見たくない場合を除き、最初にすべてのエラーを予測するのに使用するのが最適です。

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

これが問題の私のパスです。copytreeのソースコードを変更して元の機能を維持しましたが、ディレクトリが既に存在する場合でもエラーは発生しません。また、既存のファイルを上書きせずに、両方のコピーを保持するように変更しました。これは私のアプリケーションにとって重要なため、変更された名前のコピーが1つあります。

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

これを試して:

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

以下は、pathlib.Path入力としてaを期待するバージョンです。

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

この関数には、os.listdir()パスのようなオブジェクトを入力としてサポートする最初のバージョンのPythonであるPython 3.6が必要です。あなたが以前のバージョンのPythonをサポートする必要がある場合は、交換することができますlistdir(src)によってlistdir(str(src))


-2

私は最も速くて簡単な方法はpythonがシステムコマンドを呼び出すことだと思います...

例..

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

ディレクトリをtarしてgzipで圧縮します。...目的の場所にディレクトリを解凍してuntarします。

うん?


Windowsで実行している場合... 7zipをダウンロードし、コマンドラインを使用します。...もう一度提案します。
カービィ

31
システムコマンドは常に最後の手段です。コードを移植できるように、可能な限り標準ライブラリを利用することをお勧めします。
ジェサニズム2009
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.