Python verkettet Textdateien

168

Ich habe eine Liste von 20 Dateinamen, wie ['file1.txt', 'file2.txt', ...] . Ich möchte ein Python-Skript schreiben, um diese Dateien zu einer neuen Datei zu verketten. Ich könnte jede Datei durch öffnen f = open(...), Zeile für Zeile durch Aufrufen lesen f.readline()und jede Zeile in diese neue Datei schreiben. Es scheint mir nicht sehr "elegant" zu sein, besonders der Teil, in dem ich Zeile für Zeile lesen / schreiben muss.

Gibt es eine "elegantere" Möglichkeit, dies in Python zu tun?

JJ Beck
quelle
7
Es ist nicht Python, aber in Shell-Skripten könnte man so etwas tun cat file1.txt file2.txt file3.txt ... > output.txt. Wenn Sie in Python nicht mögen readline(), gibt es immer readlines()oder einfach read().
jedwards
1
@jedwards Führen Sie den cat file1.txt file2.txt file3.txtBefehl einfach mit dem subprocessModul aus und Sie sind fertig. Ich bin mir aber nicht sicher, ob es catin Windows funktioniert.
Ashwini Chaudhary
5
Die Art und Weise, wie Sie sie beschreiben, ist eine schreckliche Art, eine Datei zu lesen. Verwenden Sie die withAnweisung, um sicherzustellen, dass Ihre Dateien ordnungsgemäß geschlossen sind, und durchlaufen Sie die Datei, um Zeilen abzurufen, anstatt sie zu verwenden f.readline().
Gareth Latty
@jedwards cat funktioniert nicht, wenn die Textdatei Unicode ist.
Avi Cohen
Aktuelle Analyse waymoot.org/home/python_string
nu everest

Antworten:

257

Das sollte es tun

Für große Dateien:

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)

Für kleine Dateien:

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

… Und noch eine interessante, an die ich gedacht habe :

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)

Leider hinterlässt diese letzte Methode einige offene Dateideskriptoren, um die sich der GC sowieso kümmern sollte. Ich fand es nur interessant

inspectorG4dget
quelle
9
Dies ist bei großen Dateien sehr speichereffizient.
Gareth Latty
1
@ inspectorG4dget: Ich habe dich nicht gefragt, ich habe eyquem gefragt, wer sich beschwert hat, dass deine Lösung nicht effizient sein würde. Ich bin bereit zu wetten, dass es für den Anwendungsfall des OP und für jeden Anwendungsfall, den eyquem im Sinn hat, mehr als effizient genug ist. Wenn er der Meinung ist, dass dies nicht der Fall ist, liegt es in seiner Verantwortung, dies zu beweisen, bevor er verlangt, dass Sie es optimieren.
Abarnert
2
Was halten wir von einer großen Datei?
Dee
4
@dee: Eine Datei, die so groß ist, dass ihr Inhalt nicht in den Hauptspeicher passt
inspectorG4dget
7
Nur um es noch einmal zu wiederholen: Dies ist die falsche Antwort. Shutil.copyfileobj ist die richtige Antwort.
Paul Crowley
193

Verwenden Sie shutil.copyfileobj.

Es liest automatisch die Eingabedateien Stück für Stück für Sie, was effizienter ist und die Eingabedateien einliest und auch dann funktioniert, wenn einige der Eingabedateien zu groß sind, um in den Speicher zu passen:

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
quelle
2
for i in glob.glob(r'c:/Users/Desktop/folder/putty/*.txt'):Nun, ich habe die for-Anweisung ersetzt, um alle Dateien in das Verzeichnis aufzunehmen, aber meine output_filebegann in sehr kurzer Zeit sehr groß zu werden, wie in 100 GB.
R__raki__
10
Beachten Sie, dass die letzten Zeichenfolgen jeder Datei mit den ersten Zeichenfolgen der nächsten Datei zusammengeführt werden, wenn keine EOL-Zeichen vorhanden sind. In meinem Fall wurde das Ergebnis nach Verwendung dieses Codes vollständig beschädigt. Ich habe wfd.write (b "\ n") nach copyfileobj hinzugefügt, um ein normales Ergebnis zu erhalten
Thelambofgoat
1
@Thelambofgoat Ich würde sagen, das ist in diesem Fall keine reine Verkettung, aber hey, was auch immer Ihren Bedürfnissen entspricht.
HelloGoodbye
59

Genau dafür ist Fileinput gedacht :

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

Für diesen Anwendungsfall ist es nicht viel einfacher, als nur manuell über die Dateien zu iterieren. In anderen Fällen ist es jedoch sehr praktisch, einen einzigen Iterator zu haben, der über alle Dateien iteriert, als wären sie eine einzelne Datei. (Auch die Tatsache, dass fileinputjede Datei geschlossen wird, sobald sie fertig ist, bedeutet, dass keine withoder jede Datei erforderlich ist close, aber das ist nur eine einzeilige Einsparung, keine so große Sache.)

Es gibt noch einige andere nützliche Funktionen fileinput, z. B. die Möglichkeit, Dateien direkt durch Filtern jeder Zeile zu ändern.


Wie in den Kommentaren erwähnt und in einem anderen Beitrag besprochen , fileinputfunktioniert Python 2.7 nicht wie angegeben. Hier geringfügige Änderungen, um den Code Python 2.7-kompatibel zu machen

with open('outfilename', 'w') as fout:
    fin = fileinput.input(filenames)
    for line in fin:
        fout.write(line)
    fin.close()
abarnert
quelle
@Lattyware: Ich denke, den meisten Leuten, die etwas darüber lernen, fileinputwird gesagt, dass es eine Möglichkeit ist, eine einfache sys.argv(oder was als Argumente nach optparse/ etc. Zurückbleibt ) in eine große virtuelle Datei für triviale Skripte umzuwandeln und nicht daran zu denken, sie für irgendetwas zu verwenden sonst (dh wenn die Liste keine Befehlszeilenargumente enthält). Oder sie lernen, aber vergessen dann - ich entdecke es jedes oder jedes
zweite
1
@abament Ich denke, dies for line in fileinput.input()ist in diesem speziellen Fall nicht die beste Wahl: Das OP möchte Dateien verketten und nicht zeilenweise lesen, was theoretisch ein längerer Prozess ist
eyquem
1
@eyquem: Die Ausführung ist kein längerer Prozess. Wie Sie selbst betont haben, lesen zeilenbasierte Lösungen nicht jeweils ein Zeichen. Sie lesen Brocken ein und ziehen Zeilen aus einem Puffer. Die E / A-Zeit wird die Zeilenanalysezeit vollständig überschwemmen. Solange der Implementierer nichts schrecklich Dummes an der Pufferung getan hat, ist sie genauso schnell (und möglicherweise sogar schneller als der Versuch, einen guten Puffer zu erraten Größe selbst, wenn Sie denken, 10000 ist eine gute Wahl).
Abarnert
1
@abarnert NEIN, 10000 ist keine gute Wahl. Es ist in der Tat eine sehr schlechte Wahl, weil es keine Zweierpotenz ist und lächerlich klein ist. Bessere Größen wären 2097152 (2 21), 16777216 (2 24) oder sogar 134217728 (2 ** 27). Warum nicht? 128 MB sind nichts in einem RAM von 4 GB.
Eyquem
2
Beispielcode nicht ganz gültig für Python 2.7.10 und höher: stackoverflow.com/questions/30835090/…
CnrL
8

Ich weiß nichts über Eleganz, aber das funktioniert:

    import glob
    import os
    for f in glob.glob("file*.txt"):
         os.system("cat "+f+" >> OutFile.txt")
Daniel
quelle
8
Sie können sogar die Schleife vermeiden: import os; os.system ("cat file * .txt >> OutFile.txt")
lib
6
nicht plattformübergreifend und wird für Dateinamen mit Leerzeichen in brechen
fliegende Schafe
3
Das ist unsicher; Außerdem catkann eine Liste von Dateien erstellt werden, sodass Sie sie nicht wiederholt aufrufen müssen. Sie können es einfach sicher machen, indem Sie subprocess.check_callstattos.system
Clément
5

Was ist los mit UNIX-Befehlen? (vorausgesetzt, Sie arbeiten nicht unter Windows):

ls | xargs cat | tee output.txt erledigt den Job (Sie können ihn von Python mit Unterprozess aufrufen, wenn Sie möchten)

lucasg
quelle
21
weil dies eine Frage zu Python ist.
ObscureRobot
2
Im Allgemeinen ist nichts falsch, aber diese Antwort ist fehlerhaft (übergeben Sie die Ausgabe von ls nicht an xargs, sondern übergeben Sie die Liste der Dateien direkt an cat :) cat * | tee output.txt.
Clément
Wenn es auch Dateinamen einfügen kann, wäre das großartig.
Deqing
@Deqing Um Eingabedateinamen anzugeben, können Siecat file1.txt file2.txt | tee output.txt
GoTrained
1
... und Sie können das Senden an stdout (Drucken im Terminal) deaktivieren, indem Sie 1> /dev/nullam Ende des Befehls
GoTrained
4
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

Ein einfacher Benchmark zeigt, dass das Shutil eine bessere Leistung erbringt.

haoming
quelle
3

Eine Alternative zur @ inspectorG4dget-Antwort (beste Antwort bis zum 29.03.2016). Ich habe mit 3 Dateien von 436MB getestet.

@ inspectorG4dget Lösung: 162 Sekunden

Die folgende Lösung: 125 Sekunden

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

Die Idee ist, eine Batch-Datei zu erstellen und auszuführen, wobei die "alte gute Technologie" genutzt wird. Seine Halbpython arbeitet aber schneller. Funktioniert für Windows.

João Palma
quelle
3

Wenn sich viele Dateien im Verzeichnis befinden, ist es glob2möglicherweise besser, eine Liste mit Dateinamen zu erstellen, als sie manuell zu schreiben.

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')
Scharade
quelle
2

Überprüfen Sie die .read () -Methode des File-Objekts:

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

Sie könnten so etwas tun wie:

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

oder eine "elegantere" Python-Art:

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

was laut diesem Artikel: http://www.skymind.com/~ocrow/python_string/ auch am schnellsten wäre.

Alex Kawrykow
quelle
10
Dadurch wird eine riesige Zeichenfolge erzeugt, die je nach Größe der Dateien größer sein kann als der verfügbare Speicher. Da Python einen einfachen und langsamen Zugriff auf Dateien bietet, ist dies eine schlechte Idee.
Gareth Latty
2

Wenn die Dateien nicht gigantisch sind:

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

Wenn die Dateien zu groß sind, um vollständig gelesen und im RAM gespeichert zu werden, muss der Algorithmus etwas anders sein, um jede Datei zu lesen, die read(10000)beispielsweise von Blöcken fester Länge in einer Schleife kopiert werden soll .

eyquem
quelle
@Lattyware Weil ich mir ziemlich sicher bin, dass die Ausführung schneller ist. Übrigens, selbst wenn der Code das zeilenweise Lesen einer Datei anordnet, wird die Datei von Blöcken gelesen, die in einen Cache gestellt werden, in dem jede Zeile nacheinander gelesen wird. Das bessere Verfahren wäre, die Länge des Leseabschnitts gleich der Größe des Caches zu setzen. Aber ich weiß nicht, wie ich die Größe dieses Caches bestimmen soll.
Eyquem
Das ist die Implementierung in CPython, aber nichts davon ist garantiert. Eine solche Optimierung ist eine schlechte Idee, da sie auf einigen Systemen zwar effektiv ist, auf anderen jedoch möglicherweise nicht.
Gareth Latty
1
Ja, natürlich wird das zeilenweise Lesen gepuffert. Genau deshalb ist es nicht viel langsamer. (In einigen Fällen kann es sogar etwas schneller sein, da jeder, der Python auf Ihre Plattform portiert hat, eine viel bessere Blockgröße als 10000 gewählt hat.) Wenn die Leistung wirklich wichtig ist, müssen Sie verschiedene Implementierungen profilieren. Aber 99,99…% der Zeit ist entweder mehr als schnell genug, oder die eigentliche Festplatten-E / A ist der langsame Teil, und es spielt keine Rolle, was Ihr Code tut.
Abarnert
Wenn Sie die Pufferung wirklich manuell optimieren müssen, sollten Sie os.openund verwenden os.read, da plain openPythons Wrapper um Cs stdio verwendet, was bedeutet, dass entweder 1 oder 2 zusätzliche Puffer im Weg sind.
Abarnert
PS, warum 10000 schlecht ist: Ihre Dateien befinden sich wahrscheinlich auf einer Festplatte mit Blöcken, die eine Leistung von Bytes haben. Nehmen wir an, sie sind 4096 Bytes. Das Lesen von 10000 Bytes bedeutet also, zwei Blöcke zu lesen, dann einen Teil des nächsten. Das Lesen eines weiteren 10000 bedeutet, den Rest des nächsten zu lesen, dann zwei Blöcke, dann einen Teil des nächsten. Zählen Sie, wie viele teilweise oder vollständige Blocklesevorgänge Sie haben, und verschwenden Sie viel Zeit. Glücklicherweise werden die meisten dieser Probleme durch das Puffern und Zwischenspeichern von Python, stdio, Dateisystem und Kernel vor Ihnen verborgen bleiben. Warum sollten Sie jedoch versuchen, sie überhaupt zu erstellen?
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
quelle
-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
quelle