Python을 사용하여 파일의 전체 디렉토리를 기존 디렉토리에 복사하는 방법은 무엇입니까?


210

bar하나 이상의 파일 을 포함하는 디렉토리 와 하나 이상의 파일 을 포함하는 디렉토리를 포함하는 디렉토리에서 다음 코드를 실행하십시오 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'의 내용을 ' shutil.copytree()? '를 사용하여 'foo'에 복사 한 후 ) 더 쉬운 방법이 있습니까?


1
참고 : 여기 에 원래의 복사 트리 기능이 있습니다. 복사하여 패치하십시오 :)
schlamar

3
변경에 대한 파이썬 문제 shutil.copytree()의 행동 기존 디렉토리에 쓰기를 허용하기는하지만, 필요에 동의 할 몇 가지 행동 정보가있다.
Nick Chammas

2
: 바로 개선 요청 위에서 파이썬 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.copytree을 수행 os.makedirs(dst)시작시. 실제로 코드의 어떤 부분도 기존 디렉토리에 문제가 없습니다. 변경해야합니다. 최소한 exist_ok=False통화에 매개 변수를 제공
cfi

6
이것은 좋은 답변입니다. 그러나 아래 Mital Vora의 답변도 살펴볼 가치가 있습니다. 그렇지 않으면 같은 문제가 발생하기 때문에 shutil.copytree ()를 호출하지 않고 copytree를 재귀 적으로 호출했습니다. 아마도 Mital Vora의 답변을 병합하거나 업데이트하는 것을 고려하십시오.
PJeffes

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에는 여전히이 바보 같은 제한이 있습니다. :-(
초에 개미

5
@antred ...하지만 distutils.dir_util.copy_tree(), 이는 는 다음 stdlib에있는 그러한 제한이 없으며 실제로는 예상대로 작동합니다. 이를 감안할 때 자신의 ( 일반적으로 고장난 ) 구현 을 풀려고 시도하는 강력한 이유는 없습니다 . Brendan Abel대답 은 지금 당연히 받아 들여질 솔루션이어야합니다.
Cecil Curry

257

표준 라이브러리의 일부인 솔루션은 다음과 같습니다.

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

이 비슷한 질문을보십시오.

파이썬으로 디렉토리에 디렉토리 내용 복사


5
표준 라이브러리를 사용하기 때문에 좋은 방법입니다. 심볼릭 링크, 모드 및 시간도 보존 할 수 있습니다.
itsafire

1
작은 단점을 발견했습니다. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string즉, 받아들이지 않습니다 PosixPath. 필요합니다 str(PosixPath). 개선을위한 위시리스트 이 문제 외에도이 대답을 선호합니다.
Sun Bear

@SunBear, 그래, 경로로 문자열을 취하는 다른 대부분의 라이브러리가 그럴 것이라고 생각합니다. 객체 지향 경로 객체의 대부분의 이전 구현과 마찬가지로 Path객체를 상속 하지 않기로 선택하는 단점의 일부입니다 str.
Brendan Abel

Btw, 나는이 기능의 문서화 된 결함을 발견했습니다. 여기에 문서화되어 있습니다 . 이 기능의 사용자는 알고 있어야합니다.
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
심볼릭 링크가 있고 인수로 무시한다는 점을 제외하고는 좋지만 무시됩니다.
Matthew Alpert

FAT 파일 시스템 docs.python.org/2/library/os.html 에서 st_mtime 입도는 2 초만큼 거칠 수 있습니다 . 업데이트가 빠르게 연속되는 상황에서이 코드를 사용하면 재정의가 수행되지 않을 수 있습니다.
dgh

두 번째-마지막 줄에 버그가 있습니다. 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

다른 곳의 코드에서 'ignore'라는 함수를 정의 했습니까?
KenV99

copytree 함수를 호출하기 전에 원하는 이름으로 함수를 정의 할 수 있습니다. 이 함수 (람다 식일 수도 있음)는 두 가지 인수를 사용합니다. 디렉토리 이름과 그 안에있는 파일은 무시할 수있는 반복 파일을 반환해야합니다.
Cyrille Pontvieux

[x for x in lst if x not in excl]이것은 glob 패턴 일치를 사용하는 copytree와 동일하지 않습니다. en.wikipedia.org/wiki/Glob_(programming)
Konstantin Schubert

2
대단하다. 위의 답변에서 무시가 올바르게 사용되지 않았습니다.
Keith Holliday

21

파이썬 3.8은 다음과 같은 dirs_exist_ok주장소개 했습니다 shutil.copytree.

src 를 루트로하는 전체 디렉토리 트리를 dst 라는 디렉토리에 재귀 적으로 복사 하고 대상 디렉토리를 리턴하십시오. dirs_exist_okdst 또는 누락 된 상위 디렉토리가 이미 존재하는 경우 예외를 발생 시킬지 여부를 나타냅니다 .

따라서 Python 3.8 이상에서는 다음과 같이 작동합니다.

import shutil

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

dirs_exist_ok=False기본적으로 copytree에서 첫 번째 복사 시도가 실패하지 않습니까?
Jay

1
디렉토리가 이미 존재하는 경우에만 @Jay. 내가 왼쪽 dirs_exist_ok의 차이를 설명하기 위해 첫 번째 호출 밖으로 (그리고 디렉토리는 아직 영업 이익의 예에 존재하지 않기 때문에),하지만 당신이 원한다면 물론 당신이 그것을 사용할 수 있습니다.
크리스

감사합니다. 첫 번째 사본 근처에 의견을 추가하면 더 명확하게 보일 것입니다.
Jay

7

docs는 대상 디렉토리가 존재 하지 않아야 함을 명시 적으로 나타냅니다 .

로 이름이 지정된 대상 디렉토리가 dst아직 존재하지 않아야합니다. 상위 디렉토리가 누락 될뿐만 아니라 작성됩니다.

가장 좋은 방법은 os.walk두 번째 및 모든 후속 디렉토리, 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.

ignoreforceupdate파일 이름 또는 폴더의 목록을 기대 /는 파일 이름 을 기준으로 src 유닉스 스타일과 유사한 와일드 카드에 동의하고 glob또는 fnmatch.

이 함수는 복사 된 파일 목록을 반환합니다 (또는 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의 소스 코드를 수정했지만 디렉토리가 이미 존재하는 경우 오류가 발생하지 않습니다. 또한 기존 파일을 덮어 쓰지 않고 수정 된 이름을 가진 두 사본을 유지합니다. 응용 프로그램에 중요했기 때문입니다.

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입력 이 필요한 버전입니다 .

# 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이 필요합니다 . 파이썬의 이전 버전을 지원해야하는 경우 교체 할 수 있습니다 listdir(src)listdir(str(src)).


-2

파이썬이 시스템 명령을 호출하는 것이 가장 빠르고 간단한 방법이라고 가정합니다 ...

예..

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

타르와 gzip 디렉토리를 .... 원하는 곳에서 디렉토리를 압축 해제하고 untar.

야아?


Windows에서 실행 중이라면 7zip을 다운로드하고 명령 행을 사용하십시오. ... 다시 제안.
Kirby

31
시스템 명령은 항상 최후의 수단이어야합니다. 코드를 이식 할 수 있도록 가능한 한 항상 표준 라이브러리를 사용하는 것이 좋습니다.
jathanism
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.