Ich habe eine große Textdatei (~ 50 GB, wenn gz'ed). Die Datei enthält 4*N
Zeilen oder N
Datensätze. Das heißt, jeder Datensatz besteht aus 4 Zeilen. Ich möchte diese Datei in 4 kleinere Dateien aufteilen, die jeweils ungefähr 25% der Eingabedatei ausmachen. Wie kann ich die Datei an der Datensatzgrenze aufteilen?
Ein naiver Ansatz wäre zcat file | wc -l
, die Zeilenanzahl zu ermitteln, diese Zahl durch 4 zu teilen und dann zu verwenden split -l <number> file
. Dies geht jedoch zweimal über die Datei und der Zeilenzähler ist extrem langsam (36 Minuten). Gibt es einen besseren Weg?
Dies kommt nahe, ist aber nicht das, wonach ich suche. Die akzeptierte Antwort zählt auch die Zeilen.
BEARBEITEN:
Die Datei enthält Sequenzierungsdaten im Fastq-Format. Zwei Datensätze sehen so aus (anonymisiert):
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF
Die erste Zeile jedes Datensatzes beginnt mit a @
.
EDIT2:
zcat file > /dev/null
dauert 31 Minuten.
EDIT3:
Nur die erste Zeile beginnt mit @
. Keiner der anderen wird es jemals tun. Siehe hier . Aufzeichnungen müssen in Ordnung bleiben. Es ist nicht in Ordnung, der resultierenden Datei etwas hinzuzufügen.
zcat file > /dev/null
?@
und es gibt 4 Zeilen pro Datensatz. Sind beide absolut? - und können die Zeilen 2,3,4 beginnen@
? und gibt es einen nicht aufgezeichneten Header von Fußzeilen in der Datei?Antworten:
Ich glaube nicht, dass Sie das können - nicht zuverlässig und nicht so, wie Sie es verlangen. Die Sache ist, dass das Komprimierungsverhältnis des Archivs wahrscheinlich nicht gleichmäßig von Kopf bis Schwanz verteilt sein wird - der Komprimierungsalgorithmus wird auf einige Teile besser angewendet als auf andere. So funktioniert es einfach. Daher können Sie Ihren Split nicht anhand der Größe der komprimierten Datei berücksichtigen.
Was mehr ist ,
gzip
unterstützt nicht nur die Originalgröße von komprimierten Dateien zu speichern mehr als 4gbs Größe - es kann nicht damit umgehen. Sie können das Archiv also nicht abfragen, um eine zuverlässige Größe zu erhalten - weil es Sie täuschen wird.Die 4-Zeilen-Sache - das ist wirklich ziemlich einfach. Die 4-Datei-Sache - ich weiß nur nicht, wie Sie es zuverlässig und mit einer gleichmäßigen Verteilung machen können, ohne zuerst das Archiv zu extrahieren, um seine unkomprimierte Größe zu erhalten. Ich glaube nicht, dass du es kannst, weil ich es versucht habe.
Sie können jedoch eine maximale Größe für geteilte Ausgabedateien festlegen und sicherstellen, dass diese immer an Rekordbarrieren beschädigt werden. Das kannst du leicht machen. Hier ist ein kleines Skript, das dies tut, indem es das
gzip
Archiv extrahiert und den Inhalt durch einige explizitedd
Pipe-Puffer mit bestimmtencount=$rpt
Argumenten leitet , bevor es weitergeleitet wird,lz4
um jede Datei im laufenden Betrieb zu dekomprimieren / erneut zu komprimieren. Ich habe auch ein paar kleinetee
Pipe-Tricks reingeworfen, um die letzten vier Zeilen für jedes Segment auch an stderr zu drucken.Das geht einfach so lange weiter, bis alle Eingaben verarbeitet wurden. Es wird nicht versucht, es um einen bestimmten Prozentsatz zu teilen - den es nicht erhalten kann -, sondern es wird nach einer maximalen Anzahl von Rohbytes pro Teilung aufgeteilt. Ein großer Teil Ihres Problems besteht darin, dass Sie keine zuverlässige Größe für Ihr Archiv erhalten können, weil es zu groß ist - was auch immer Sie tun, tun Sie das nicht noch einmal - machen Sie die Splits weniger als 4 GB pro Stück , vielleicht. Zumindest mit diesem kleinen Skript können Sie dies tun, ohne jemals ein unkomprimiertes Byte auf die Festplatte schreiben zu müssen.
Hier ist eine kürzere Version, die auf das Wesentliche reduziert ist - sie fügt nicht alle Berichte hinzu:
Es macht alle die gleichen Dinge wie das erste, meistens hat es einfach nicht so viel zu sagen. Außerdem gibt es weniger Unordnung, sodass Sie vielleicht leichter sehen können, was los ist.
Die
IFS=
Sache ist nur, die eineread
Zeile pro Iteration zu behandeln. Wirread
eins, weil wir unsere Schleife brauchen, um zu enden, wenn die Eingabe endet. Dies hängt von Ihrem rekord Größe -, die pro Ihr Beispiel ist 354 Bytes pro. Ich habe ein 4 + GB-gzip
Archiv mit einigen zufälligen Daten erstellt, um es zu testen.Die zufälligen Daten wurden folgendermaßen erhalten:
... aber vielleicht müssen Sie sich darüber nicht so viele Sorgen machen, da Sie bereits über die Daten und alles verfügen. Zurück zur Lösung ...
Grundsätzlich
pigz
- was etwas schneller zu dekomprimieren scheintzcat
- leitet der unkomprimierte Stream und diedd
Puffer, die in Schreibblöcke ausgegeben werden, die speziell mit einem Vielfachen von 354 Bytes dimensioniert sind, weiter. Die Schleife wirdread
ein$line
einmal jede Iteration zu testen , die Eingabe noch ankommen, was es wirdprintf
danachprintf
an ,lz4
bevor ein anderedd
genannten Blöcke zu lesen , mit einem Mehrfachen bemessen speziell von 354 Bytes - zum Synchronisieren mit dem Pufferprozessdd
- für die Dauer. Aufgrund der Initiale wird es einen kurzen Lesevorgang pro Iteration gebenread $line
- aber das spielt keine Rolle, da wir diesenlz4
ohnehin bei - unserem Collector-Prozess - drucken .Ich habe es so eingerichtet, dass jede Iteration ungefähr 1 GB unkomprimierte Daten liest und diesen In-Stream auf ungefähr 650 MB oder so komprimiert.
lz4
ist weitaus schneller als so ziemlich jede andere nützliche Komprimierungsmethode - weshalb ich sie hier gewählt habe, weil ich nicht gerne warte.xz
würde wahrscheinlich beim eigentlichen Komprimieren einen viel besseren Job machen. Eine Sachelz4
ist jedoch, dass es häufig mit einer RAM-Geschwindigkeit dekomprimiert werden kann - was bedeutet, dass Sie einlz4
Archiv häufig nur schnell dekomprimieren können, da Sie es ohnehin in den Speicher schreiben könnten.Der Große macht ein paar Berichte pro Iteration. Beide Schleifen drucken
dd
den Bericht über die Anzahl der übertragenen Rohbytes und die Geschwindigkeit und so weiter. Die große Schleife druckt auch die letzten 4 Eingabezeilen pro Zyklus und eine Byteanzahl für dieselbe, gefolgt von einemls
der Verzeichnisse, in die ich dielz4
Archive schreibe . Hier sind einige Ausgaberunden:quelle
gzip -l
funktioniert nur für <2GiB unkomprimierte Dateien IIRC (etwas kleiner als die OP-Datei sowieso).Das Aufteilen von Dateien an den Datensatzgrenzen ist ohne Code sehr einfach:
Dadurch werden Ausgabedateien mit jeweils 10000 Zeilen mit den Namen output_name_aa, output_name_ab, output_name_ac, ... erstellt. Bei einer so großen Eingabe wie Ihrer erhalten Sie viele Ausgabedateien. Durch
10000
ein Vielfaches von vier ersetzen , und Sie können die Ausgabedateien so groß oder klein machen, wie Sie möchten. Leider gibt es, wie bei den anderen Antworten, keine gute Möglichkeit, um zu gewährleisten, dass Sie die gewünschte Anzahl von (ungefähr) gleich großen Ausgabedateien erhalten, ohne Vermutungen über die Eingabe anzustellen. (Oder leiten Sie das Ganze tatsächlich durchwc
.) Wenn Ihre Datensätze ungefähr gleich groß (oder zumindest ungefähr gleichmäßig verteilt) sind, können Sie versuchen, eine Schätzung wie diese zu erstellen:Dadurch wird die komprimierte Größe der ersten 1000 Datensätze Ihrer Datei angezeigt. Auf dieser Grundlage können Sie wahrscheinlich eine Schätzung erstellen, wie viele Zeilen in jeder Datei vier Dateien enthalten sollen. (Wenn Sie nicht möchten, dass eine entartete fünfte Datei übrig bleibt, sollten Sie Ihre Schätzung ein wenig auffüllen oder die fünfte Datei am Ende der vierten anheften.)
Bearbeiten: Hier ist noch ein Trick, vorausgesetzt, Sie möchten komprimierte Ausgabedateien:
Dadurch werden viele kleinere Dateien erstellt und diese dann schnell wieder zusammengefügt. (Je nachdem, wie lang die Zeilen in Ihren Dateien sind, müssen Sie möglicherweise den Parameter -l anpassen.) Es wird davon ausgegangen, dass Sie eine relativ aktuelle Version von GNU-Coreutils (für Split-Filter) und etwa 130% Ihrer Eingabedateigröße in haben freier Speicherplatz. Ersetzen Sie pigz / unpigz durch gzip / zcat, wenn Sie sie nicht haben. Ich habe gehört, dass einige Softwarebibliotheken (Java?) Auf diese Weise verkettete gzip-Dateien nicht verarbeiten können, aber ich hatte bisher keine Probleme damit. (pigz verwendet denselben Trick, um die Komprimierung zu parallelisieren.)
quelle
Nach dem, was ich nach dem Überprüfen der Google-Sphäre und dem weiteren Testen einer 7,8-GiB-
.gz
Datei zusammengetragen habe, scheinen die Metadaten der Größe der unkomprimierten Originaldatei für große Dateien nicht korrekt (dh falsch ) zu sein.gz
(größer als 4 GB (für einige möglicherweise 2 GB)) Versionen vongzip
).Re. mein Test der Metadaten von gzip:
Es scheint also nicht möglich zu sein, die unkomprimierte Größe zu bestimmen, ohne sie tatsächlich zu dekomprimieren (was, gelinde gesagt, etwas rau ist!).
Hier ist eine Möglichkeit, eine unkomprimierte Datei an Datensatzgrenzen zu teilen, wobei jeder Datensatz 4 Zeilen enthält .
Es verwendet die Dateigröße in Bytes (via
stat
) und beimawk
Zählen von Bytes (keine Zeichen). Gibt an, ob das ZeilenendeLF
| istCR
|CRLF
Dieses Skript verarbeitet die Länge des Zeilenendes über die integrierte VariableRT
.Unten ist der Test, mit dem ich überprüft habe, ob die Zeilenanzahl jeder Datei stimmt
mod 4 == 0
Testausgabe:
myfile
wurde generiert von:quelle
Dies ist keine ernsthafte Antwort! Ich habe nur damit gespieltflex
und dies wird höchstwahrscheinlich bei einer Eingabedatei mit ~ 50 GB nicht funktionieren (wenn überhaupt, bei größeren Eingabedaten als meiner Testdatei):Dies funktioniert für mich in einer ~ 1 GB-Datei input.txt :
Angesichts der
flex
Eingabedatei splitter.l :Erzeugen lex.yy.c und an die Kompilierung
splitter
Binärdatei mit:Verwendungszweck:
Laufzeit für 1 GB input.txt :
quelle
getc(stream)
einfach an und wenden Sie eine einfache Logik an. Weißt du auch, dass die. (Punkt) Regex-Zeichen in (f) Lex entspricht jedem Zeichen außer Zeilenumbruch , oder? Während diese Datensätze mehrzeilig sind.@
Zeichen erkennt , und dann die Daten durch die Standardregel kopieren lassen. Jetzt kopiert Ihre Regel einen Teil der Daten als ein großes Token, und dann erhält die Standardregel die zweite Zeile zeichenweise.txr
.Hier ist eine Lösung in Python, bei der die Eingabedatei einmal durchlaufen wird und die Ausgabedateien im weiteren Verlauf geschrieben werden.
Eine Funktion zur Verwendung
wc -l
besteht darin, dass Sie davon ausgehen, dass alle Datensätze hier dieselbe Größe haben. Das mag hier zutreffen, aber die folgende Lösung funktioniert auch dann, wenn dies nicht der Fall ist. Es verwendet im Grundewc -c
oder die Anzahl der Bytes in der Datei. In Python erfolgt dies über os.stat ()So funktioniert das Programm. Wir berechnen zunächst die idealen Teilungspunkte als Byte-Offsets. Anschließend lesen Sie die Zeilen der Eingabedatei und schreiben sie in die entsprechende Ausgabedatei. Wenn Sie feststellen, dass Sie den optimalen nächsten Teilungspunkt überschritten haben und sich an einer Datensatzgrenze befinden, schließen Sie die letzte Ausgabedatei und öffnen Sie die nächste.
Das Programm ist in diesem Sinne optimal, es liest die Bytes der Eingabedatei einmal; Zum Abrufen der Dateigröße müssen die Dateidaten nicht gelesen werden. Der benötigte Speicherplatz ist proportional zur Größe einer Zeile. Aber Python oder das System haben vermutlich vernünftige Dateipuffer, um die E / A zu beschleunigen.
Ich habe Parameter hinzugefügt, wie viele Dateien geteilt werden sollen und wie groß der Datensatz ist, falls Sie dies in Zukunft anpassen möchten.
Und dies könnte natürlich auch in andere Programmiersprachen übersetzt werden.
Eine andere Sache, ich bin nicht sicher, ob Windows mit seiner crlf die Länge der Zeile richtig handhabt, wie es auf Unix-y-Systemen tut. Wenn len () hier um eins deaktiviert ist, hoffe ich, dass es offensichtlich ist, wie das Programm angepasst werden kann.quelle
printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Benutzer FloHimself schien neugierig auf eine TXR- Lösung zu sein. Hier ist eine, die das eingebettete TXR Lisp verwendet :
Anmerkungen:
Aus dem gleichen Grund ist es
pop
wichtig, jedes Tupel aus der Lazy-Liste der Tupel abzutasten, damit die Lazy-Liste verbraucht wird. Wir dürfen keinen Verweis auf den Anfang dieser Liste behalten, da dann der Speicher wächst, wenn wir durch die Datei marschieren.(seek-stream fo 0 :from-current)
ist ein No-Op-Fall vonseek-stream
, der sich durch Rückgabe der aktuellen Position nützlich macht.Leistung: Erwähne es nicht. Verwendbar, bringt aber keine Trophäen nach Hause.
Da wir die Größenprüfung nur alle 1000 Tupel durchführen, können wir die Tupelgröße nur auf 4000 Zeilen festlegen.
quelle
Wenn Sie nicht möchten, dass die neuen Dateien zusammenhängende Teile der Originaldatei sind, können Sie dies
sed
auf folgende Weise vollständig tun :Das verhindert,
-n
dass jede Zeile gedruckt wird, und jedes der-e
Skripte macht im Wesentlichen dasselbe.1~16
entspricht der ersten Zeile und jeder 16. Zeile danach.,+3
bedeutet, dass die nächsten drei Zeilen nach jeder dieser Zeilen übereinstimmen.w1.txt
sagt, schreibe alle diese Zeilen in die Datei1.txt
. Dies nimmt jede 4. Gruppe von 4 Zeilen und schreibt sie in eine Datei, beginnend mit der ersten Gruppe von 4 Zeilen. Die anderen drei Befehle machen dasselbe, aber sie werden jeweils um 4 Zeilen nach vorne verschoben und in eine andere Datei geschrieben.Dies wird schrecklich kaputt gehen, wenn die Datei nicht genau der von Ihnen festgelegten Spezifikation entspricht, aber ansonsten sollte sie wie beabsichtigt funktionieren. Ich habe es nicht profiliert, daher weiß ich nicht, wie effizient es sein wird, aber es
sed
ist einigermaßen effizient bei der Stream-Bearbeitung.quelle