Arquivos de texto concatenados em Python

168

Eu tenho uma lista de 20 nomes de arquivos, como ['file1.txt', 'file2.txt', ...]. Eu quero escrever um script Python para concatenar esses arquivos em um novo arquivo. Eu poderia abrir cada arquivo f = open(...), ler linha por linha chamando f.readline()e escrever cada linha nesse novo arquivo. Não me parece muito "elegante", especialmente a parte em que tenho que ler // escrever linha por linha.

Existe uma maneira mais "elegante" de fazer isso no Python?

JJ Beck
fonte
7
Não é python, mas em scripts de shell você pode fazer algo parecido cat file1.txt file2.txt file3.txt ... > output.txt. Em python, se você não gosta readline(), sempre readlines()ou simplesmente read().
Jedwards
1
@jedwards simplesmente execute o cat file1.txt file2.txt file3.txtcomando usando o subprocessmódulo e pronto. Mas não tenho certeza se catfunciona no Windows.
Ashwini Chaudhary
5
Como uma observação, a maneira como você descreve é ​​uma maneira terrível de ler um arquivo. Use a withinstrução para garantir que seus arquivos sejam fechados corretamente e itere sobre o arquivo para obter linhas, em vez de usar f.readline().
precisa
O gato @jedwards não funciona quando o arquivo de texto é unicode.
Avi Cohen
Análise real waymoot.org/home/python_string
nu everest

Respostas:

258

Isso deve servir

Para arquivos grandes:

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for fname in filenames:
        with open(fname) as infile:
            for line in infile:
                outfile.write(line)

Para arquivos pequenos:

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for fname in filenames:
        with open(fname) as infile:
            outfile.write(infile.read())

... e outro interessante que eu pensei :

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for line in itertools.chain.from_iterable(itertools.imap(open, filnames)):
        outfile.write(line)

Infelizmente, esse último método deixa alguns descritores de arquivos abertos, dos quais o GC deve cuidar de qualquer maneira. Eu só pensei que isso era interessante

inspectorG4dget
fonte
9
Isso, para arquivos grandes, será muito ineficiente em memória.
precisa
1
@ inspectorG4dget: Eu não estava perguntando a você, estava perguntando a eyquem, que reclamou que sua solução não seria eficiente. Estou disposto a apostar que é mais do que eficiente o suficiente para o caso de uso do OP e para qualquer caso de uso que o eyquem tenha em mente. Se ele acha que não, é responsabilidade dele provar isso antes de exigir que você o otimize.
abarnert
2
o que estamos considerando ser um arquivo grande ?
Dee
4
@dee: um arquivo tão grande que o seu conteúdo não caber na memória principal
inspectorG4dget
7
Apenas para reiterar: esta é a resposta errada, shutil.copyfileobj é a resposta certa.
Paul Crowley
193

Use shutil.copyfileobj.

Ele lê automaticamente os arquivos de entrada pedaço por pedaço para você, o que é mais eficiente e a leitura dos arquivos de entrada e funcionará mesmo que alguns arquivos de entrada sejam muito grandes para caber na memória:

import shutil

with open('output_file.txt','wb') as wfd:
    for f in ['seg1.txt','seg2.txt','seg3.txt']:
        with open(f,'rb') as fd:
            shutil.copyfileobj(fd, wfd)
Miau
fonte
2
for i in glob.glob(r'c:/Users/Desktop/folder/putty/*.txt'):bem, eu substituí a instrução for para incluir todos os arquivos no diretório, mas output_filecomecei a crescer realmente enorme como em centenas de gb em tempo muito rápido.
precisa saber é o seguinte
10
Observe que isso mesclará as últimas seqüências de cada arquivo com as primeiras do próximo arquivo, se não houver caracteres EOL. No meu caso, obtive um resultado totalmente corrompido depois de usar este código. Eu adicionei wfd.write (b "\ n") após o copyfileobj para obter o resultado normal
Thelambofgoat 18/02/19
1
@ Thelambofgoat Eu diria que não é uma concatenação pura nesse caso, mas, ei, o que for mais adequado às suas necessidades.
HelloGoodbye 18/10/19
59

É exatamente para isso que serve o fileinput :

import fileinput
with open(outfilename, 'w') as fout, fileinput.input(filenames) as fin:
    for line in fin:
        fout.write(line)

Para este caso de uso, não é realmente muito mais simples do que apenas repetir os arquivos manualmente, mas em outros casos, é muito útil ter um único iterador que itere sobre todos os arquivos como se fossem um único arquivo. (Além disso, o fato de que fileinputfecha cada arquivo assim que ele é feito significa que não há necessidade de withou closecada um, mas isso é apenas uma poupança de uma linha, não que grande de um negócio.)

Existem outros recursos interessantes fileinput, como a capacidade de fazer modificações no local dos arquivos apenas filtrando cada linha.


Conforme observado nos comentários e discutido em outro post , o fileinputPython 2.7 não funcionará como indicado. Aqui, uma pequena modificação para tornar o código compatível com Python 2.7

with open('outfilename', 'w') as fout:
    fin = fileinput.input(filenames)
    for line in fin:
        fout.write(line)
    fin.close()
abarnert
fonte
@ Lattyware: Eu acho que a maioria das pessoas que aprendem fileinputé informada de que é uma maneira de transformar um simples sys.argv(ou o que resta como argumento após optparse/ etc.) Em um grande arquivo virtual para scripts triviais, e não pensa em usá-lo para nada else (ou seja, quando a lista não for argumentos de linha de comando). Ou eles aprender, mas, em seguida, esquecer-I manter re descobrindo-lo a cada ano ou dois ...
abarnert
1
@abament Eu acho que for line in fileinput.input()não é a melhor maneira de escolher, neste caso particular: o OP quer arquivos concatenar, não lê-los linha por linha que é um processo teoricamente mais tempo para executar
Eyquem
1
@eyquem: Não é um processo mais longo para executar. Como você mesmo apontou, as soluções baseadas em linhas não leem um caractere de cada vez; eles lêem em pedaços e tiram linhas de um buffer. O tempo de E / S alterará completamente o tempo de análise de linha, desde que o implementador não tenha feito algo terrivelmente estúpido no buffer, será tão rápido (e possivelmente até mais rápido do que tentar adivinhar um bom buffer) dimensione-se, se você acha que 10000 é uma boa escolha).
abarnert
1
@abarnert NÃO, 10000 não é uma boa escolha. É realmente uma escolha muito ruim, porque não é uma potência de 2 e é ridiculamente um tamanho pequeno. Tamanhos melhores seriam 2097152 (2 21), 16777216 (2 24) ou até 134217728 (2 ** 27), por que não?, 128 MB não é nada em uma RAM de 4 GB.
Eyquem
2
Código de exemplo não muito válido para Python 2.7.10 e posterior: stackoverflow.com/questions/30835090/…
CnrL
8

Eu não sei sobre elegância, mas isso funciona:

    import glob
    import os
    for f in glob.glob("file*.txt"):
         os.system("cat "+f+" >> OutFile.txt")
Daniel
fonte
8
você pode até evitar o loop: import os; os.system ("arquivo de gato * .txt >> OutFile.txt")
lib
6
não é uma plataforma cruzada e quebrará para nomes de arquivos com espaços neles
flying sheep o
3
Isso é inseguro; Além disso, catpode levar uma lista de arquivos, portanto, não é necessário chamá-lo repetidamente. Você pode facilmente torná-lo seguro chamando em subprocess.check_callvez deos.system
Clément
5

O que há de errado com os comandos UNIX? (considerando que você não está trabalhando no Windows):

ls | xargs cat | tee output.txt faz o trabalho (você pode chamá-lo de python com subprocesso, se quiser)

lucasg
fonte
21
porque esta é uma pergunta sobre python.
ObscureRobot #
2
Nada de errado em geral, mas esta resposta está quebrada (não passe a saída de ls para xargs, apenas passe a lista de arquivos para cat diretamente:) cat * | tee output.txt.
Clément
Se ele também pode inserir o nome do arquivo, isso seria ótimo.
Deqing
@Deqing Para especificar os nomes dos arquivos de entrada, você pode usarcat file1.txt file2.txt | tee output.txt
GoTrained
1
... e você pode desativar o envio para stdout (impressão no Terminal) adicionando 1> /dev/nullao final do comando
GoTrained
4
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

Uma referência simples mostra que o obturador tem um desempenho melhor.

haoming
fonte
3

Uma alternativa à resposta do @ inspectorG4dget (melhor resposta até a data 29-03-2016). Eu testei com 3 arquivos de 436MB.

@ inspectorG4dget solução: 162 segundos

A seguinte solução: 125 segundos

from subprocess import Popen
filenames = ['file1.txt', 'file2.txt', 'file3.txt']
fbatch = open('batch.bat','w')
str ="type "
for f in filenames:
    str+= f + " "
fbatch.write(str + " > file4results.txt")
fbatch.close()
p = Popen("batch.bat", cwd=r"Drive:\Path\to\folder")
stdout, stderr = p.communicate()

A idéia é criar um arquivo em lote e executá-lo, aproveitando a "velha boa tecnologia". É semi-python, mas funciona mais rápido. Funciona para janelas.

João Palma
fonte
3

Se você tiver muitos arquivos no diretório, glob2poderá ser uma opção melhor para gerar uma lista de nomes de arquivos em vez de escrevê-los manualmente.

import glob2

filenames = glob2.glob('*.txt')  # list of all .txt files in the directory

with open('outfile.txt', 'w') as f:
    for file in filenames:
        with open(file) as infile:
            f.write(infile.read()+'\n')
Sharad
fonte
2

Confira o método .read () do objeto File:

http://docs.python.org/2/tutorial/inputoutput.html#methods-of-file-objects

Você poderia fazer algo como:

concat = ""
for file in files:
    concat += open(file).read()

ou uma maneira python mais 'elegante':

concat = ''.join([open(f).read() for f in files])

que, de acordo com este artigo: http://www.skymind.com/~ocrow/python_string/ , também seria o mais rápido.

Alex Kawrykow
fonte
10
Isso produzirá uma cadeia gigante, que, dependendo do tamanho dos arquivos, poderá ser maior que a memória disponível. Como o Python fornece acesso fácil e preguiçoso aos arquivos, é uma má idéia.
precisa
2

Se os arquivos não forem gigantescos:

with open('newfile.txt','wb') as newf:
    for filename in list_of_files:
        with open(filename,'rb') as hf:
            newf.write(hf.read())
            # newf.write('\n\n\n')   if you want to introduce
            # some blank lines between the contents of the copied files

Se os arquivos forem grandes demais para serem totalmente lidos e mantidos na RAM, o algoritmo deverá ser um pouco diferente para ler cada arquivo a ser copiado em um loop por pedaços de comprimento fixo, usando, read(10000)por exemplo.

eyquem
fonte
@ Lattyware Porque tenho certeza de que a execução é mais rápida. Aliás, mesmo quando o código ordena a leitura de um arquivo linha por linha, o arquivo é lido por blocos, que são colocados em cache no qual cada linha é lida uma após a outra. O melhor procedimento seria colocar o tamanho do pedaço de leitura igual ao tamanho do cache. Mas não sei como determinar o tamanho desse cache.
Eyquem
Essa é a implementação no CPython, mas nada disso é garantido. Otimizar como essa é uma má idéia, pois, embora possa ser eficaz em alguns sistemas, pode não ser em outros.
Gareth Latty
1
Sim, é claro que a leitura linha por linha é armazenada em buffer. É exatamente por isso que não é muito mais lento. (De fato, em alguns casos, pode até ser um pouco mais rápido, porque quem transportou o Python para sua plataforma escolheu um tamanho de chunk muito melhor que 10000.) Se o desempenho disso realmente importa, você terá que criar perfis diferentes. Mas 99,99…% do tempo, de qualquer forma, é mais do que rápido o suficiente, ou a E / S de disco real é a parte mais lenta e não importa o que seu código faça.
abarnert
Além disso, se você realmente precisar otimizar manualmente o buffer, será melhor usá-lo os.opene os.read, porque plain openusa os wrappers do Python em torno do stdio do C, o que significa que 1 ou 2 buffers extras estão no seu caminho.
abarnert
PS, por que 10000 é ruim: seus arquivos provavelmente estão em um disco, com blocos com algum poder de bytes. Digamos que sejam 4096 bytes. Portanto, ler 10000 bytes significa ler dois blocos e parte do próximo. Ler outros 10000 significa ler o restante do próximo, depois dois blocos e parte do próximo. Conte quantas leituras parciais ou completas você tem e você está perdendo muito tempo. Felizmente, o buffer e o cache do Python, do stdio, do sistema de arquivos e do kernel esconderão a maioria desses problemas, mas por que tentar criá-los em primeiro lugar?
abarnert
0
def concatFiles():
    path = 'input/'
    files = os.listdir(path)
    for idx, infile in enumerate(files):
        print ("File #" + str(idx) + "  " + infile)
    concat = ''.join([open(path + f).read() for f in files])
    with open("output_concatFile.txt", "w") as fo:
        fo.write(path + concat)

if __name__ == "__main__":
    concatFiles()
user2825287
fonte
-2
  import os
  files=os.listdir()
  print(files)
  print('#',tuple(files))
  name=input('Enter the inclusive file name: ')
  exten=input('Enter the type(extension): ')
  filename=name+'.'+exten
  output_file=open(filename,'w+')
  for i in files:
    print(i)
    j=files.index(i)
    f_j=open(i,'r')
    print(f_j.read())
    for x in f_j:
      outfile.write(x)
VasanthOPT
fonte