Como copiar um diretório inteiro de arquivos em um diretório existente usando Python?

210

Execute o código a seguir em um diretório que contenha um diretório denominado bar(contendo um ou mais arquivos) e um diretório denominado baz(também contendo um ou mais arquivos). Verifique se não há um diretório chamado foo.

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

Irá falhar com:

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

Quero que isso funcione da mesma maneira como se eu tivesse digitado:

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

Preciso usar shutil.copy()para copiar cada arquivo bazem foo? (Depois de já ter copiado o conteúdo de 'bar' para 'foo' shutil.copytree()?) Ou existe uma maneira mais fácil / melhor?

Daryl Spitzer
fonte
1
FYI: aqui é a função copytree original, basta copiar e corrigi-lo :)
schlamar
3
Há um problema do Python sobre a alteração shutil.copytree()do comportamento para permitir a gravação em um diretório existente, mas há alguns detalhes de comportamento que precisam ser acordados.
Nick Chammas
2
Apenas observando que a solicitação de aprimoramento mencionada acima foi implementada para o Python 3.8: docs.python.org/3.8/whatsnew/3.8.html#shutil
ncoghlan

Respostas:

174

Essa limitação do padrão shutil.copytreeparece arbitrária e irritante. Gambiarra:

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)

Observe que não é totalmente consistente com o padrão copytree:

  • não honra symlinkse ignoreparâmetros para o diretório raiz da srcárvore;
  • não gera shutil.Errorerros no nível raiz de src;
  • em caso de erros durante a cópia de uma subárvore, ela aumentará shutil.Errorpara essa subárvore em vez de tentar copiar outras subárvores e aumentar uma única combinada shutil.Error.
atzz
fonte
50
Obrigado! Concorde que isso parece totalmente arbitrário! shutil.copytreefaz um os.makedirs(dst)no início. Nenhuma parte do código realmente teria um problema com um diretório preexistente. Isso precisa ser mudado. Pelo menos forneça um exist_ok=Falseparâmetro para a chamada
cfi
6
Esta é uma boa resposta - no entanto, a resposta de Mital Vora abaixo também vale a pena analisar. Eles chamaram copytree recursivamente em vez de shutil.copytree () porque o mesmo problema surgirá de outra forma. Possivelmente considere mesclar respostas ou atualizar as de Mital Vora.
PJeffes
4
Isso falhará se for fornecido um caminho que inclua um diretório que não esteja vazio no destino. Talvez alguém poderia resolver isso com recursão de cauda, mas aqui está uma modificação em seu código que funcionadef 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, super irritante. Passados ​​quatro anos, o shutil.copytree ainda possui essa restrição boba. :-(
antred
5
@ antred ... mas distutils.dir_util.copy_tree(), que também reside no stdlib, não tem essa restrição e realmente se comporta conforme o esperado. Dado isso, não há motivo convincente para tentar desenrolar sua própria implementação ( ... normalmente quebrada ). A resposta de Brendan Abel deve ser absolutamente a solução aceita agora.
Cecil Curry
257

Aqui está uma solução que faz parte da biblioteca padrão:

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

Veja esta pergunta semelhante.

Copie o conteúdo do diretório para um diretório com python

Brendan Abel
fonte
5
Essa é boa porque usa a biblioteca padrão. Links simbólicos, modo e hora também podem ser preservados.
28816 itsafire
1
Percebeu uma pequena desvantagem. distutils.errors.DistutilsInternalError: mkpath: 'name' must be a string, ou seja, não aceita PosixPath. Precisa str(PosixPath). Lista de desejos para melhoria. Fora isso, prefiro esta resposta.
Sun Bear
@ SunBear, sim, acho que será o caso da maioria das outras bibliotecas que tomam caminhos como strings. Parte da desvantagem para a escolha de não fazer o Pathobjeto herdam strSuponho que, como a maioria das implementações anteriores de orientação a objeto objetos caminho ..
Brendan Abel
Btw, me deparei com uma deficiência documentada dessa função. Está documentado aqui . Os usuários desta função foram aconselhados a estar cientes disso.
Sun Bear
1
Enquanto "tecnicamente público", observe que os desenvolvedores do distutils deixaram claro (mesmo link que o @ SunBear's, thx!) Que distutils.dir_util.copy_tree()é considerado um detalhe de implementação do distutils e não é recomendado para uso público. A solução real deve ser para shutil.copytree()ser aprimorada / estendida para se comportar de maneira mais semelhante distutils.dir_util.copy_tree(), mas sem suas deficiências. Enquanto isso, continuarei usando funções auxiliares personalizadas semelhantes às fornecidas em outras respostas.
Boris Dalstein
61

Em ligeira melhora na resposta do atzz à função em que a função acima sempre tenta copiar os arquivos da origem para o destino.

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)

Na minha implementação acima

  • Criando o diretório de saída, se ainda não existir
  • Fazendo o diretório de cópia chamando recursivamente meu próprio método.
  • Quando chegamos a copiar o arquivo, verifico se o arquivo foi modificado, apenas devemos copiar.

Estou usando a função acima, juntamente com a construção de scons. Isso me ajudou muito, pois toda vez que eu compilar, talvez não seja necessário copiar todo o conjunto de arquivos ... mas apenas os arquivos modificados.

Mital Vora
fonte
4
Bom, exceto que você tem links simbólicos e ignora como argumentos, mas eles são ignorados.
Matthew Alpert
Vale notar que a granularidade st_mtime pode ser tão grossa quanto 2 segundos nos sistemas de arquivos FAT docs.python.org/2/library/os.html . Usando esse código em um contexto em que as atualizações acontecem em rápida sucessão, você pode achar que as substituições não ocorrem.
dgh
Há um erro na penúltima linha, deve ser: if not os.path.exists(d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
mpderbec
34

Uma mesclagem inspirada em 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)
  • Mesmo comportamento que o shutil.copytree , com links simbólicos e parâmetros de ignorar
  • Criar estrutura de destino de diretório se não existir
  • Não falhará se o dst já existir
Cyrille Pontvieux
fonte
Isso é muito mais rápido que a solução original quando o aninhamento de diretório é profundo. Obrigado
Kashif
Você definiu uma função também chamada 'ignorar' no código em outro lugar?
KenV99
Você pode definir qualquer função com qualquer nome que desejar antes de chamar a função copytree. Essa função (que também pode ser uma expressão lambda) recebe dois argumentos: um nome de diretório e os arquivos nele, deve retornar uma iterável de ignorar arquivos.
Cyrille Pontvieux
[x for x in lst if x not in excl]isso não faz o mesmo que o copytree, que usa a correspondência de padrões globais. en.wikipedia.org/wiki/Glob_(programming)
Konstantin Schubert
2
Isso é ótimo. A ignição não estava sendo utilizada corretamente na resposta acima.
Keith Holliday
21

Python 3.8 introduziu o dirs_exist_okargumento para shutil.copytree:

Copie recursivamente uma árvore de diretório inteira com raiz no src para um diretório chamado dst e retorne o diretório de destino. dirs_exist_ok determina se deve ser lançada uma exceção caso o dst ou qualquer diretório pai ausente já exista.

Portanto, com o Python 3.8+, isso deve funcionar:

import shutil

shutil.copytree('bar', 'foo')
shutil.copytree('baz', 'foo', dirs_exist_ok=True)
Chris
fonte
dirs_exist_ok=Falsepor padrão no copytree, a primeira tentativa de cópia falhará?
Jay
1
@ Jay, apenas se o diretório já existir. Eu deixei de dirs_exist_okfora a primeira chamada para ilustrar a diferença (e porque o diretório ainda não existe no exemplo do OP), mas é claro que você pode usá-lo se quiser.
Chris
Obrigado, se você adicionar um comentário próximo à primeira cópia, acho que ficaria mais claro :)
Jay
7

Os documentos afirmam explicitamente que o diretório de destino não deve existir :

O diretório de destino, nomeado por dst, ainda não deve existir; ele será criado, além de diretórios-pai ausentes.

Eu acho que sua melhor aposta é para os.walko segundo e todos os diretórios, copy2diretórios e arquivos consequentes e fazer mais copystatpara diretórios. Afinal, é exatamente o que copytreefaz, conforme explicado nos documentos. Ou você poderia copye copystatcada diretório / arquivo e, em os.listdirvez de os.walk.

SilentGhost
fonte
1

Isso é inspirado na melhor resposta original fornecida pelo atzz, acabei de adicionar a lógica de substituição de arquivos / pastas. Portanto, ele não se mescla, mas exclui o arquivo / pasta existente e copia o novo:

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)

Remova o comentário da rmtree para torná-la uma função de movimentação.

Radtek
fonte
0

Aqui está a minha versão da mesma tarefa:

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

Aqui está uma versão inspirada neste tópico que imita mais de perto distutils.file_util.copy_file.

updateonlyé um booleano se True, copiará apenas arquivos com datas modificadas mais recentes que os arquivos existentes, a dstmenos que listado na forceupdatequal copiará independentemente.

ignoree forceupdateespere listas de nomes de arquivos ou pastas / nomes de arquivos relativos src e aceite curingas no estilo Unix semelhantes a globou fnmatch.

A função retorna uma lista de arquivos copiados (ou seriam copiados se dryrunforem 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
fonte
0

A solução anterior tem algum problema que srcpode sobrescrever dstsem nenhuma notificação ou exceção.

Eu adiciono um predict_errormétodo para prever erros antes da cópia. copytreeprincipalmente baseado na versão de Cyrille Pontvieux.

Usar predict_errorpara prever todos os erros no início é o melhor, a menos que você queira ver a exceção gerada uma por outra ao executar copytreeaté corrigir todos os erros.

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

Aqui está o meu passe para o problema. Modifiquei o código-fonte do copytree para manter a funcionalidade original, mas agora nenhum erro ocorre quando o diretório já existe. Também mudei para que não sobrescreva os arquivos existentes, mas mantenha as duas cópias, uma com um nome modificado, pois isso era importante para o meu aplicativo.

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

Tente isto:

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

Aqui está uma versão que espera a pathlib.Pathcomo entrada.

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

Observe que esta função requer o Python 3.6, que é a primeira versão do Python, onde os.listdir()suporta objetos semelhantes a caminhos como entrada. Se você precisar oferecer suporte a versões anteriores do Python, poderá substituí-lo listdir(src)por listdir(str(src)).

Boris Dalstein
fonte
-2

Eu assumiria maneira mais rápida e simples seria python chamar os comandos do sistema ...

exemplo..

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

Tar e gzip o diretório .... descompacte e descompacte o diretório no local desejado.

yah?

Kirby
fonte
se você estiver executando no Windows ... faça o download do 7zip .. e use a linha de comando para isso. ... novamente apenas sugestões.
Kirby
31
Os comandos do sistema devem sempre ser o último recurso. É sempre melhor utilizar a biblioteca padrão sempre que possível, para que seu código seja portátil.
jathanism