Python concatena archivos de texto

168

Tengo una lista de 20 nombres de archivo, como ['file1.txt', 'file2.txt', ...]. Quiero escribir un script de Python para concatenar estos archivos en un nuevo archivo. Podía abrir cada archivo f = open(...), leer línea por línea llamando f.readline()y escribir cada línea en ese nuevo archivo. No me parece muy "elegante", especialmente la parte donde tengo que leer // escribir línea por línea.

¿Hay alguna forma más "elegante" de hacer esto en Python?

JJ Beck
fuente
77
No es Python, pero en las secuencias de comandos de shell podrías hacer algo así cat file1.txt file2.txt file3.txt ... > output.txt. En Python, si no te gusta readline(), siempre lo hay readlines()o simplemente read().
jedwards
1
@jedwards simplemente ejecuta el cat file1.txt file2.txt file3.txtcomando usando el subprocessmódulo y listo. Pero no estoy seguro si catfunciona en Windows.
Ashwini Chaudhary
55
Como nota, la forma en que describe es una forma terrible de leer un archivo. Use la withdeclaración para asegurarse de que sus archivos estén cerrados correctamente e itere sobre el archivo para obtener líneas, en lugar de usarlas f.readline().
Gareth Latty
@jedwards cat no funciona cuando el archivo de texto es unicode.
Avi Cohen
Análisis real waymoot.org/home/python_string
nu everest

Respuestas:

259

Esto debería hacerlo

Para archivos 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 archivos pequeños:

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

... y otra interesante en la que pensé :

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)

Lamentablemente, este último método deja algunos descriptores de archivos abiertos, de los cuales el GC debería ocuparse de todos modos. Solo pensé que era interesante

inspectorG4dget
fuente
9
Esto, para archivos grandes, será muy ineficiente en la memoria.
Gareth Latty
1
@ inspectorG4dget: no te preguntaba, preguntaba a eyquem, quien se quejaba de que tu solución no iba a ser eficiente. Estoy dispuesto a apostar que es más que eficiente para el caso de uso del OP, y para cualquier caso de uso que eyquem tenga en mente. Si él piensa que no lo es, es su responsabilidad probar eso antes de exigirle que lo optimice.
abarnert
2
¿Qué consideramos que es un archivo grande ?
Dee
44
@dee: un archivo tan grande que su contenido no cabe en la memoria principal
inspectorG4dget
77
Solo para reiterar: esta es la respuesta incorrecta, shutil.copyfileobj es la respuesta correcta.
Paul Crowley
193

Uso shutil.copyfileobj.

Lee automáticamente los archivos de entrada trozo por trozo, lo que es más eficiente y lee los archivos de entrada y funcionará incluso si algunos de los archivos de entrada son demasiado grandes para caber en la memoria:

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)
maullar
fuente
2
for i in glob.glob(r'c:/Users/Desktop/folder/putty/*.txt'):bueno, reemplacé la declaración for para incluir todos los archivos en el directorio, pero output_filecomencé a crecer realmente en cientos de gb en muy poco tiempo.
R__raki__
10
Tenga en cuenta que se fusionarán las últimas cadenas de cada archivo con las primeras cadenas del siguiente archivo si no hay caracteres EOL. En mi caso, obtuve un resultado totalmente dañado después de usar este código. Agregué wfd.write (b "\ n") después de copyfileobj para obtener un resultado normal
Thelambofgoat
1
@Thelambofgoat Diría que no es una concatenación pura en ese caso, pero bueno, lo que se adapte a tus necesidades.
HelloGoodbye
59

Para eso es exactamente la entrada de archivos :

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, en realidad no es mucho más simple que simplemente iterar sobre los archivos manualmente, pero en otros casos, tener un solo iterador que itera sobre todos los archivos como si fueran un solo archivo es muy útil. (Además, el hecho de que fileinputcierre cada archivo tan pronto como esté hecho significa que no es necesario withni closecada uno, pero eso es solo un ahorro de una línea, no es un gran problema).

Hay algunas otras características ingeniosas fileinput, como la capacidad de hacer modificaciones in situ de archivos simplemente filtrando cada línea.


Como se señaló en los comentarios y se discutió en otra publicación , fileinputpara Python 2.7 no funcionará como se indica. Aquí una ligera modificación para hacer que el código sea compatible con Python 2.7

with open('outfilename', 'w') as fout:
    fin = fileinput.input(filenames)
    for line in fin:
        fout.write(line)
    fin.close()
abarnert
fuente
@Lattyware: Creo que a la mayoría de las personas que aprenden se fileinputles dice que es una forma de convertir un simple sys.argv(o lo que queda como argumentos después de optparse/ etc.) En un gran archivo virtual para scripts triviales, y no piensan usarlo para nada de lo contrario (es decir, cuando la lista no es args de línea de comandos). O aprenden, pero luego se olvidan: sigo descubriéndolo cada año o dos ...
abarnert
1
@abament, creo for line in fileinput.input()que no es la mejor manera de elegir en este caso particular: el OP quiere concatenar archivos, no leerlos línea por línea, que es un proceso teóricamente más largo para ejecutar
eyquem
1
@eyquem: No es un proceso más largo para ejecutar. Como usted mismo señaló, las soluciones basadas en líneas no leen un carácter a la vez; leen en trozos y extraen líneas de un búfer. El tiempo de E / S reducirá por completo el tiempo de análisis de línea, por lo que siempre que el implementador no haya hecho algo horriblemente estúpido en el almacenamiento en búfer, será igual de rápido (y posiblemente incluso más rápido que tratar de adivinar un buen búfer) dimensionarse, si cree que 10000 es una buena opción).
abarnert
1
@abarnert NO, 10000 no es una buena opción. De hecho, es una muy mala elección porque no tiene una potencia de 2 y es ridículamente de un tamaño pequeño. Los mejores tamaños serían 2097152 (2 21), 16777216 (2 24) o incluso 134217728 (2 ** 27), ¿por qué no? 128 MB no es nada en una RAM de 4 GB.
eyquem
2
Código de ejemplo no bastante válido para Python 2.7.10 y posterior: stackoverflow.com/questions/30835090/…
CnrL
8

No sé sobre elegancia, pero esto funciona:

    import glob
    import os
    for f in glob.glob("file*.txt"):
         os.system("cat "+f+" >> OutFile.txt")
Daniel
fuente
8
incluso puedes evitar el ciclo: import os; os.system ("cat file * .txt >> OutFile.txt")
lib
66
no cruza la plataforma y se romperá para los nombres de archivo con espacios en ellos
ovejas voladoras
3
Esto es inseguro; Además, catpuede tomar una lista de archivos, por lo que no es necesario llamarla repetidamente. Puedes hacerlo fácilmente llamando al en subprocess.check_calllugar deos.system
Clément
5

¿Qué hay de malo con los comandos UNIX? (dado que no está trabajando en Windows):

ls | xargs cat | tee output.txt hace el trabajo (puede llamarlo desde python con subproceso si lo desea)

lucasg
fuente
21
porque esta es una pregunta sobre python.
ObscureRobot
2
No hay nada malo en general, pero esta respuesta está rota (no pase la salida de ls a xargs, solo pase la lista de archivos a cat directamente:) cat * | tee output.txt.
Clément
Si también puede insertar un nombre de archivo, sería genial.
Deqing
@Deqing Para especificar los nombres de los archivos de entrada, puede usarcat file1.txt file2.txt | tee output.txt
GoTrained
1
... y puede deshabilitar el envío a stdout (imprimir en la Terminal) agregando 1> /dev/nullal final del comando
GoTrained
4
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

Un punto de referencia simple muestra que el shutil funciona mejor.

haoming
fuente
3

Una alternativa a la respuesta @ inspectorG4dget (la mejor respuesta hasta la fecha 29-03-2016). Probé con 3 archivos de 436MB.

@ inspectorG4dget solución: 162 segundos

La siguiente solución: 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()

La idea es crear un archivo por lotes y ejecutarlo, aprovechando la "tecnología buena y antigua". Es semi-pitón pero funciona más rápido. Funciona para ventanas.

João Palma
fuente
3

Si tiene muchos archivos en el directorio, entonces glob2podría ser una mejor opción para generar una lista de nombres de archivos en lugar de escribirlos a mano.

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
fuente
2

Consulte el método .read () del objeto File:

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

Podrías hacer algo como:

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

o una forma de pitón más "elegante":

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

que, según este artículo: http://www.skymind.com/~ocrow/python_string/ también sería el más rápido.

Alex Kawrykow
fuente
10
Esto producirá una cadena gigante que, dependiendo del tamaño de los archivos, podría ser más grande que la memoria disponible. Como Python proporciona un fácil acceso diferido a los archivos, es una mala idea.
Gareth Latty
2

Si los archivos no son 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

Si los archivos son demasiado grandes para leerlos por completo y guardarlos en la RAM, el algoritmo debe ser un poco diferente para leer cada archivo que se copiará en un bucle por fragmentos de longitud fija, read(10000)por ejemplo.

eyquem
fuente
@Lattyware Porque estoy bastante seguro de que la ejecución es más rápida. Por cierto, incluso cuando el código ordena leer un archivo línea por línea, el archivo se lee por fragmentos, que se guardan en la memoria caché en la que cada línea se lee una tras otra. El mejor procedimiento sería poner la longitud del fragmento de lectura igual al tamaño de la memoria caché. Pero no sé cómo determinar el tamaño de este caché.
eyquem
Esa es la implementación en CPython, pero nada de eso está garantizado. Optimizar así es una mala idea, ya que si bien puede ser eficaz en algunos sistemas, puede no serlo en otros.
Gareth Latty
1
Sí, por supuesto, la lectura línea por línea está protegida. Eso es exactamente por qué no es mucho más lento. (De hecho, en algunos casos, incluso puede ser un poco más rápido, porque quien haya portado Python a su plataforma eligió un tamaño de fragmento mucho mejor que 10000). Si el rendimiento de esto realmente importa, tendrá que perfilar diferentes implementaciones. Pero el 99,99% del tiempo, de cualquier manera es más que lo suficientemente rápido, o la E / S del disco real es la parte lenta y no importa lo que haga su código.
abarnert
Además, si usted realmente necesidad de optimizar manualmente el almacenamiento temporal, tendrá que utilizar os.openy os.read, a causa de civil openenvoltorios usos de Python alrededor de stdio C, lo que significa 1 o 2 amortiguadores adicionales en su camino.
abarnert
PD, por qué 10000 es malo: sus archivos probablemente estén en un disco, con bloques que tienen una potencia de bytes de longitud. Digamos que son 4096 bytes. Entonces, leer 10000 bytes significa leer dos bloques, luego parte del siguiente. Leer otros 10000 significa leer el resto del siguiente, luego dos bloques, luego parte del siguiente. Cuente cuántas lecturas de bloque parciales o completas tiene, y está perdiendo mucho tiempo. Afortunadamente, el almacenamiento en caché y el almacenamiento en caché de Python, stdio, sistema de archivos y kernel te ocultarán la mayoría de estos problemas, pero ¿por qué intentar crearlos en primer 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()
usuario2825287
fuente
-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
fuente