Python concatena i file di testo


168

Ho un elenco di 20 nomi di file, come ['file1.txt', 'file2.txt', ...]. Voglio scrivere uno script Python per concatenare questi file in un nuovo file. Potrei aprire ogni file f = open(...), leggere riga per riga chiamando f.readline()e scrivere ogni riga in quel nuovo file. Non mi sembra molto "elegante", specialmente la parte in cui devo leggere // scrivere riga per riga.

Esiste un modo più "elegante" per farlo in Python?


7
Non è Python, ma negli script di shell potresti fare qualcosa del genere cat file1.txt file2.txt file3.txt ... > output.txt. In Python, se non ti piace readline(), c'è sempre readlines()o semplicemente read().
jedwards,

1
@jedwards esegui semplicemente il cat file1.txt file2.txt file3.txtcomando utilizzando il subprocessmodulo e il gioco è fatto. Ma non sono sicuro che catfunzioni in Windows.
Ashwini Chaudhary,

5
Come nota, il modo in cui descrivi è un modo terribile di leggere un file. Utilizzare l' withistruzione per assicurarsi che i file siano chiusi correttamente e iterare sul file per ottenere le linee anziché utilizzare f.readline().
Gareth Latty,

Il gatto @jedwards non funziona quando il file di testo è unicode.
Avi Cohen,

Risposte:


259

Questo dovrebbe farlo

Per file di grandi dimensioni:

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)

Per file piccoli:

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 un altro interessante a cui ho pensato :

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)

Purtroppo, quest'ultimo metodo lascia alcuni descrittori di file aperti, di cui il GC dovrebbe occuparsi comunque. Ho solo pensato che fosse interessante


9
Questo, per file di grandi dimensioni, sarà molto inefficiente dalla memoria.
Gareth Latty,

1
@ inspectorG4dget: non ti stavo chiedendo, stavo chiedendo eyquem, che si è lamentato del fatto che la tua soluzione non sarebbe stata efficiente. Sono disposto a scommettere che è più che abbastanza efficiente per il caso d'uso dell'OP e per qualsiasi caso d'uso abbia in mente eyquem. Se pensa che non lo sia, è sua responsabilità dimostrarlo prima di chiedere di ottimizzarlo.
abarnert,

2
cosa stiamo considerando un file di grandi dimensioni?
Dal

4
@dee: un file così grande che il suo contenuto non si adatta alla memoria principale
inspectorG4dget

7
Giusto per ribadire: questa è la risposta sbagliata, shutil.copyfileobj è la risposta giusta.
Paul Crowley,

193

Usa shutil.copyfileobj.

Legge automaticamente i file di input pezzo per pezzo, che è più efficiente e legge i file di input e funzionerà anche se alcuni dei file di input sono troppo grandi per adattarsi alla 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)

2
for i in glob.glob(r'c:/Users/Desktop/folder/putty/*.txt'):bene ho sostituito la dichiarazione for per includere tutti i file nella directory, ma il mio ha output_fileiniziato a crescere davvero enorme come in 100 di GB in tempi molto rapidi.
R__raki__

10
Nota, cioè unirà le ultime stringhe di ciascun file con le prime stringhe del file successivo se non ci sono caratteri EOL. Nel mio caso ho ottenuto un risultato totalmente danneggiato dopo aver usato questo codice. Ho aggiunto wfd.write (b "\ n") dopo copyfileobj per ottenere un risultato normale
Thelambofgoat,

1
@Thelambofgoat Direi che non è una pura concatenazione in quel caso, ma ehi, qualunque cosa soddisfi le tue esigenze.
Ciao Arrivederci

59

Questo è esattamente lo scopo di fileinput :

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

Per questo caso d'uso, in realtà non è molto più semplice della semplice iterazione manuale dei file, ma in altri casi, avere un singolo iteratore che scorre su tutti i file come se fossero un singolo file è molto utile. (Inoltre, il fatto che fileinputchiuda ogni file non appena viene eseguito significa che non è necessario witho closeciascuno di essi, ma è solo un risparmio di una riga, non è un grosso problema.)

Ci sono alcune altre funzionalità intelligenti fileinput, come la possibilità di apportare modifiche sul posto dei file semplicemente filtrando ogni riga.


Come notato nei commenti e discusso in un altro post , fileinputper Python 2.7 non funzionerà come indicato. Qui una leggera modifica per rendere conforme il codice Python 2.7

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

@Lattyware: Penso che alla maggior parte delle persone che hanno appreso fileinputsia stato detto che è un modo per trasformare un semplice sys.argv(o ciò che resta come args dopo optparse/ ecc.) In un grande file virtuale per script banali, e non pensare di usarlo per nulla else (ovvero, quando l'elenco non è args da riga di comando). Oppure imparano, ma poi dimenticano: continuo a riscoprirlo ogni anno o due ...
abarnert,

1
@abament Penso che for line in fileinput.input()non sia il modo migliore di scegliere in questo caso particolare: l'OP vuole concatenare i file, non leggerli riga per riga che è un processo teoricamente più lungo da eseguire
eyquem

1
@eyquem: non è un processo più lungo da eseguire. Come hai sottolineato tu stesso, le soluzioni basate su linee non leggono un carattere alla volta; leggono a pezzi e tirano fuori le righe da un buffer. Il tempo di I / O sommergerà completamente il tempo di analisi della linea, quindi finché l'implementatore non ha fatto qualcosa di orribilmente stupido nel buffering, sarà altrettanto veloce (e forse anche più veloce del tentativo di indovinare un buon buffer taglia te stesso, se pensi che 10000 sia una buona scelta).
abarnert,

1
@abarnert NO, 10000 non è una buona scelta. È davvero una pessima scelta perché non è una potenza di 2 ed è ridicolmente di piccole dimensioni. Le dimensioni migliori sarebbero 2097152 (2 21), 16777216 (2 24) o addirittura 134217728 (2 ** 27), perché no? 128 MB non è nulla in una RAM di 4 GB.
eyquem,

2
Esempio di codice non del tutto valido per Python 2.7.10 e versioni successive: stackoverflow.com/questions/30835090/...
CNRL

8

Non conosco l'eleganza, ma questo funziona:

    import glob
    import os
    for f in glob.glob("file*.txt"):
         os.system("cat "+f+" >> OutFile.txt")

8
puoi persino evitare il ciclo: import os; os.system ("cat file * .txt >> OutFile.txt")
lib

6
non multipiattaforma e si spezzerà per i nomi dei file con spazi all'interno
volare pecore il

3
Questo non è sicuro; inoltre, catpuò prendere un elenco di file, quindi non è necessario chiamarlo ripetutamente. Puoi facilmente renderlo sicuro chiamando subprocess.check_callinvece dios.system
Clément il

5

Cosa c'è che non va nei comandi UNIX? (dato che non stai lavorando su Windows):

ls | xargs cat | tee output.txt fa il lavoro (puoi chiamarlo da Python con sottoprocesso se vuoi)


21
perché questa è una domanda su Python.
ObscureRobot,

2
Nulla di sbagliato in generale, ma questa risposta è rotta (non passare l'output di ls a xargs, passa semplicemente l'elenco dei file direttamente a cat:) cat * | tee output.txt.
Clément,

Se fosse in grado di inserire anche il nome file sarebbe fantastico.
Deqing,

@Deqing Per specificare i nomi dei file di input, è possibile utilizzarecat file1.txt file2.txt | tee output.txt
GoTrained il

1
... e puoi disabilitare l'invio a stdout (stampa nel Terminale) aggiungendo 1> /dev/nullalla fine del comando
GoTrained

4
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

Un semplice benchmark mostra che lo shutil ha prestazioni migliori.


3

Un'alternativa alla risposta @ inspectorG4dget (migliore risposta alla data 29-03-2016). Ho provato con 3 file di 436 MB.

@ inspectorG4dget soluzione: 162 secondi

La seguente soluzione: 125 secondi

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

L'idea è quella di creare un file batch ed eseguirlo, sfruttando la "vecchia buona tecnologia". È semi-pitone ma funziona più velocemente. Funziona per Windows.


3

Se nella directory sono presenti molti file, glob2potrebbe essere un'opzione migliore per generare un elenco di nomi di file anziché scriverli 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')

2

Scopri il metodo .read () dell'oggetto File:

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

Potresti fare qualcosa del tipo:

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

o un modo più 'elegante' di pitone:

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

che, secondo questo articolo: http://www.skymind.com/~ocrow/python_string/ sarebbe anche il più veloce.


10
Questo produrrà una stringa gigante, che, a seconda della dimensione dei file, potrebbe essere più grande della memoria disponibile. Dato che Python fornisce un facile accesso pigro ai file, è una cattiva idea.
Gareth Latty,

2

Se i file non sono giganteschi:

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 i file sono troppo grandi per essere interamente letti e conservati nella RAM, l'algoritmo deve essere leggermente diverso per leggere ogni file per essere copiato in un ciclo da blocchi di lunghezza fissa, usando read(10000)ad esempio.


@Lattyware Perché sono abbastanza sicuro che l'esecuzione è più veloce. A proposito, infatti, anche quando il codice ordina di leggere un file riga per riga, il file viene letto da blocchi, che vengono inseriti nella cache in cui ciascuna riga viene quindi letta una dopo l'altra. La procedura migliore sarebbe quella di mettere la lunghezza del blocco di lettura uguale alla dimensione della cache. Ma non so come determinare le dimensioni di questa cache.
eyquem,

Questa è l'implementazione in CPython, ma nulla di tutto ciò è garantito. Ottimizzare in questo modo è una cattiva idea poiché, sebbene possa essere efficace su alcuni sistemi, potrebbe non esserlo su altri.
Gareth Latty,

1
Sì, ovviamente la lettura riga per riga è bufferizzata. Questo è esattamente il motivo per cui non è molto più lento. (In effetti, in alcuni casi, potrebbe anche essere leggermente più veloce, perché chiunque ha portato Python sulla tua piattaforma ha scelto una dimensione del blocco molto migliore di 10000.) Se le prestazioni di questo sono davvero importanti, dovrai profilare diverse implementazioni. Ma il 99,99 ...% delle volte, in entrambi i casi è più che abbastanza veloce, o l'I / O del disco effettivo è la parte lenta e non importa cosa fa il tuo codice.
abarnert,

Inoltre, se hai davvero bisogno di ottimizzare manualmente il buffering, ti consigliamo di utilizzarlo os.opene os.read, poiché plain openusa i wrapper di Python attorno allo stdio di C, il che significa che 1 o 2 buffer extra ti si frappongono.
abarnert,

PS, perché il motivo per cui 10000 è male: i tuoi file sono probabilmente su un disco, con blocchi lunghi un po 'di byte. Diciamo che sono 4096 byte. Quindi, leggere 10000 byte significa leggere due blocchi, quindi parte del successivo. Leggere un altro 10000 significa leggere il resto del successivo, quindi due blocchi, quindi parte del successivo. Conta il numero di letture di blocchi parziali o complete che hai e stai perdendo molto tempo. Fortunatamente, Python, stdio, filesystem e buffering e cache del kernel nasconderanno la maggior parte di questi problemi, ma perché provare a crearli in primo luogo?
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()

-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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.