Wie kann eine große Textdatei effizient aufgeteilt werden, ohne dass mehrzeilige Datensätze aufgeteilt werden?


9

Ich habe eine große Textdatei (~ 50 GB, wenn gz'ed). Die Datei enthält 4*NZeilen oder NDatensä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.


Wie lange dauert eine einzelne zcat file > /dev/null?
Choroba

Können Sie ein kleines Beispiel der betreffenden Datei bereitstellen?
FloHimself

Sie sagen, jeder Datensatz beginnt mit @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?
Peter.O

1
Suchen Sie eine Lösung, die komprimierte Eingaben verarbeitet und / oder komprimierte Ausgaben erzeugt? Suchen Sie vier gleich große komprimierte Dateien?
Stephen Kitt

Antworten:


4

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 , gzipunterstü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 gzipArchiv extrahiert und den Inhalt durch einige explizite ddPipe-Puffer mit bestimmten count=$rptArgumenten leitet , bevor es weitergeleitet wird, lz4um jede Datei im laufenden Betrieb zu dekomprimieren / erneut zu komprimieren. Ich habe auch ein paar kleine teePipe-Tricks reingeworfen, um die letzten vier Zeilen für jedes Segment auch an stderr zu drucken.

(       IFS= n= c=$(((m=(k=1024)*k)/354))
        b=bs=354xk bs=bs=64k
        pigz -d </tmp/gz | dd i$bs o$b |
        while   read -r line _$((n+=1))
        do      printf \\n/tmp/lz4.$n\\n
        { {     printf %s\\n "$line"
                dd count=$c i$b o$bs
        }|      tee /dev/fd/3|lz4 -BD -9 >/tmp/lz4.$n
        } 3>&1| tail -n4 |tee /dev/fd/2 |
                wc -c;ls -lh /tmp/[gl]z*
        done
)

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:

(       IFS= n= c=$((1024*1024/354))
        pigz -d | dd ibs=64k obs=354xk |
        while   read -r line _$((n+=1))
        do {    printf %s\\n "$line"
                dd count=$c obs=64k ibs=354xk
        }  |    lz4 -BD -9  >/tmp/lz4.$n
        done
)  </tmp/gz

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 eine readZeile pro Iteration zu behandeln. Wir readeins, 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- gzipArchiv mit einigen zufälligen Daten erstellt, um es zu testen.

Die zufälligen Daten wurden folgendermaßen erhalten:

(       mkfifo /tmp/q; q="$(echo '[1+dPd126!<c]sc33lcx'|dc)"
        (tr '\0-\33\177-\377' "$q$q"|fold -b144 >/tmp/q)&
        tr '\0-\377' '[A*60][C*60][G*60][N*16][T*]' | fold -b144 |
        sed 'h;s/^\(.\{50\}\)\(.\{8\}\)/@N\1+\2\n/;P;s/.*/+/;H;x'|
        paste "-d\n" - - - /tmp/q| dd bs=4k count=kx2k  | gzip
)       </dev/urandom >/tmp/gz 2>/dev/null

... 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 scheint zcat- leitet der unkomprimierte Stream und die ddPuffer, die in Schreibblöcke ausgegeben werden, die speziell mit einem Vielfachen von 354 Bytes dimensioniert sind, weiter. Die Schleife wird readein $lineeinmal jede Iteration zu testen , die Eingabe noch ankommen, was es wird printfdanach printfan , lz4bevor ein andere ddgenannten 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 geben read $line- aber das spielt keine Rolle, da wir diesen lz4ohnehin 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. lz4ist weitaus schneller als so ziemlich jede andere nützliche Komprimierungsmethode - weshalb ich sie hier gewählt habe, weil ich nicht gerne warte. xzwürde wahrscheinlich beim eigentlichen Komprimieren einen viel besseren Job machen. Eine Sache lz4ist jedoch, dass es häufig mit einer RAM-Geschwindigkeit dekomprimiert werden kann - was bedeutet, dass Sie ein lz4Archiv 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 ddden 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 einem lsder Verzeichnisse, in die ich die lz4Archive schreibe . Hier sind einige Ausgaberunden:

/tmp/lz4.1
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.838 s, 6.3 MB/s
@NTACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGC+TCTCTNCC
TACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGCTCTCTNCCGAGCTCAGTATGTTNNAAGTCCTGANGNGTNGCGCCTACCCGACCACAACCTCTACTCGGTTCCGCATGCATGCAACACATCGTCA
+
I`AgZgW*,`Gw=KKOU:W5dE1m=-"9W@[AG8;<P7P6,qxE!7P4##,Q@c7<nLmK_u+IL4Kz.Rl*+w^A5xHK?m_JBBhqaLK_,o;p,;QeEjb|">Spg`MO6M'wod?z9m.yLgj4kvR~+0:.X#(Bf
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1

/tmp/lz4.2
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.38 s, 6.3 MB/s
@NTTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGAC+CTTTTGCT
TTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGACCTTTTGCTGCCCTGGTACTTTTGTCTGACTGGGGGTGCCACTTGCAGNAGTAAAAGCNAGCTGGTTCAACNAATAAGGACNANTTNCACTGAAC
+
>G-{N~Q5Z5QwV??I^~?rT+S0$7Pw2y9MV^BBTBK%HK87(fz)HU/0^%JGk<<1--7+r3e%X6{c#w@aA6Q^DrdVI0^8+m92vc>RKgnUnMDcU:j!x6u^g<Go?p(HKG@$4"T8BWZ<z.Xi
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:35 /tmp/lz4.2

gzip -lfunktioniert nur für <2GiB unkomprimierte Dateien IIRC (etwas kleiner als die OP-Datei sowieso).
Stéphane Chazelas

@ StéphaneChazelas - verdammt. Nur so kann ich mir vorstellen, eine unkomprimierte Größe zu erhalten. Ohne das funktioniert das überhaupt nicht.
Mikeserv

4

Das Aufteilen von Dateien an den Datensatzgrenzen ist ohne Code sehr einfach:

zcat your_file.gz | split -l 10000 - output_name_

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 10000ein 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 durch wc.) 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:

zcat your_file.gz | head -n4000 | gzip | wc -c

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:

#!/bin/sh

base=$(basename $1 .gz)
unpigz -c $1 | split -l 100000 --filter='pigz -c > _$FILE.gz' - ${base}_

batch=$((`ls _*.gz | wc -l` / 4 + 1))
for i in `seq 1 4`; do
  files=`ls _*.gz | head -$batch`
  cat $files > ${base}_$i.gz && rm $files
done

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


Wenn Sie pigz installiert haben, können Sie die Dinge ein wenig beschleunigen, indem Sie 'pigcat -cd' durch 'zcat' ersetzen.
Drew

2
Ah, ich habe gerade bemerkt, dass Sie bereits die Trennung in der Frage erwähnt haben. Aber wirklich, fast jede Lösung wird ungefähr das Gleiche tun wie unter der Haube gespalten. Der schwierige Teil besteht darin, herauszufinden, wie viele Zeilen Sie in jede Datei einfügen müssen.
Drew

3

Nach dem, was ich nach dem Überprüfen der Google-Sphäre und dem weiteren Testen einer 7,8-GiB- .gzDatei 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 von gzip).
Re. mein Test der Metadaten von gzip:

* The compressed.gz file is  7.8 GiB ( 8353115038 bytes) 
* The uncompressed  file is 18.1 GiB (19436487168 bytes)
* The metadata says file is  2.1 GiB ( 2256623616 bytes) uncompressed

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 beim awkZählen von Bytes (keine Zeichen). Gibt an, ob das Zeilenende LF| ist CR| CRLFDieses Skript verarbeitet die Länge des Zeilenendes über die integrierte Variable RT.

LC_ALL=C gawk 'BEGIN{"stat -c %s "ARGV[1] | getline inSize
                      segSiz=int(inSize/4)+((inSize%4)==0?0:1)
                      ouSplit=segSiz; segNb=0 }
               { lnb++; bytCt+=(length+length(RT))
                 print $0 > ARGV[1]"."segNb
                 if( lnb!=4 ) next
                 lnb=0
                 if( bytCt>=ouSplit ){ segNb++; ouSplit+=segSiz }
               }' myfile

Unten ist der Test, mit dem ich überprüft habe, ob die Zeilenanzahl jeder Datei stimmt mod 4 == 0

for i in myfile  myfile.{0..3}; do
    lc=$(<"$i" wc -l)
    printf '%s\t%s\t' "$i" $lc; 
    (( $(echo $lc"%4" | bc) )) && echo "Error: mod 4 remainder !" || echo 'mod 4 ok'  
done | column -ts$'\t' ;echo

Testausgabe:

myfile    1827904  mod 4 ok
myfile.0  456976   mod 4 ok
myfile.1  456976   mod 4 ok
myfile.2  456976   mod 4 ok
myfile.3  456976   mod 4 ok

myfile wurde generiert von:

printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4} > myfile

2

Dies ist keine ernsthafte Antwort! Ich habe nur damit gespielt flexund 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 flexEingabedatei splitter.l :

%{
#include <stdio.h>
extern FILE* yyin;
extern FILE* yyout;

int input_size = 0;

int part_num;
int part_num_max;
char **part_names;
%}

%%
@.+ {
        if (ftell(yyout) >= input_size / part_num_max) {
            fclose(yyout);
            if ((yyout = fopen(part_names[++part_num], "w")) == 0) {
                exit(1);
            }
        }
        fprintf(yyout, "%s", yytext);
    }
%%

int main(int argc, char *argv[]) {

    if (argc < 2) {
        return 1;
    } else if ((yyin = fopen(argv[1], "r")) == 0) {
        return 1;
    } else if ((yyout = fopen(argv[2], "w")) == 0) {
        fclose(yyin);
        return 1;
    } else {

        fseek(yyin, 0L, SEEK_END);
        input_size = ftell(yyin);
        rewind(yyin);

        part_num = 0;
        part_num_max = argc - 2;
        part_names = argv + 2;

        yylex();

        fclose(yyin);
        fclose(yyout);
        return 0;
    }
}

Erzeugen lex.yy.c und an die Kompilierung splitterBinärdatei mit:

$ flex splitter.l && gcc lex.yy.c -ll -o splitter

Verwendungszweck:

$ ./splitter input.txt output.part1 output.part2 output.part3 output.part4

Laufzeit für 1 GB input.txt :

$ time ./splitter input.txt output.part1 output.part2 output.part3 output.part4

real    2m43.640s
user    0m48.100s
sys     0m1.084s

Das eigentliche Lexing hier ist so einfach, dass Sie wirklich nicht von Lex profitieren. Rufen Sie 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.
Kaz

@Kaz Während Ihre Aussagen im Allgemeinen korrespondieren, funktioniert dies tatsächlich mit den Daten in Q.
FloHimself

Nur aus Versehen, da es eine Standardregel gibt, wenn nichts übereinstimmt: Verbrauchen Sie ein Zeichen und drucken Sie es in die Ausgabe! Mit anderen Worten, Sie können Ihre Datei einfach mit einer Regel wechseln, die das @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.
Kaz

Danke fürs klarstellen. Ich frage mich, wie würden Sie diese Aufgabe lösen txr.
FloHimself

Ich bin mir nicht sicher, ob ich das tun würde, denn die Aufgabe besteht darin, eine sehr einfache Sache mit einer großen Datenmenge so schnell wie möglich zu erledigen.
Kaz

1

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 -lbesteht 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 Grunde wc -coder 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.

#!/usr/bin/env python
import os

# Adjust these
filename = 'file.txt'
rec_size = 4
file_splits = 4

size = os.stat(filename).st_size
splits = [(i+1)*size/file_splits for i in range(file_splits)]
with open(filename, 'r') as fd:
    linecount = 0
    i = 0 # File split number
    out = open('file%d.txt' % i, 'w')
    offset = 0  # byte offset of where we are in the file: 0..size
    r = 0 # where we are in the record: 0..rec_size-1
    for line in fd:
        linecount += 1
        r = (r+1) % rec_size
        if offset + len(line) > splits[i] and r == 1 :
            out.close()
            i += 1
            out = open('file%d.txt' % i, 'w')
        out.write(line)
        offset += len(line)
    out.close()
    print("file %s has %d lines" % (filename, linecount))

Es wird nicht an einer Datensatzgrenze aufgeteilt. z.B. Die erste Aufteilung der printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Unterdatei

1

Benutzer FloHimself schien neugierig auf eine TXR- Lösung zu sein. Hier ist eine, die das eingebettete TXR Lisp verwendet :

(defvar splits 4)
(defvar name "data")

(let* ((fi (open-file name "r"))                 ;; input stream
       (rc (tuples 4 (get-lines fi)))            ;; lazy list of 4-tuples
       (sz (/ (prop (stat name) :size) splits))  ;; split size
       (i 1)                                     ;; split enumerator
       (n 0)                                     ;; tuplecounter within split
       (no `@name.@i`)                           ;; output split file name
       (fo (open-file no "w")))                  ;; output stream
  (whilet ((r (pop rc)))  ;; pop each 4-tuple
    (put-lines r fo) ;; send 4-tuple into output file
    ;; if not on the last split, every 1000 tuples, check the output file
    ;; size with stat and switch to next split if necessary.
    (when (and (< i splits)
               (> (inc n) 1000)
               (>= (seek-stream fo 0 :from-current) sz))
      (close-stream fo)
      (set fo (open-file (set no `@name.@(inc i)`) "w")
           n 0)))
  (close-stream fo))

Anmerkungen:

  1. Aus dem gleichen Grund ist es popwichtig, 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.

  2. (seek-stream fo 0 :from-current)ist ein No-Op-Fall von seek-stream, der sich durch Rückgabe der aktuellen Position nützlich macht.

  3. Leistung: Erwähne es nicht. Verwendbar, bringt aber keine Trophäen nach Hause.

  4. Da wir die Größenprüfung nur alle 1000 Tupel durchführen, können wir die Tupelgröße nur auf 4000 Zeilen festlegen.


0

Wenn Sie nicht möchten, dass die neuen Dateien zusammenhängende Teile der Originaldatei sind, können Sie dies sedauf folgende Weise vollständig tun :

sed -n -e '1~16,+3w1.txt' -e '5~16,+3w2.txt' -e '9~16,+3w3.txt' -e '13~16,+3w4.txt'

Das verhindert, -ndass jede Zeile gedruckt wird, und jedes der -eSkripte macht im Wesentlichen dasselbe. 1~16entspricht der ersten Zeile und jeder 16. Zeile danach. ,+3bedeutet, dass die nächsten drei Zeilen nach jeder dieser Zeilen übereinstimmen. w1.txtsagt, schreibe alle diese Zeilen in die Datei 1.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 sedist einigermaßen effizient bei der Stream-Bearbeitung.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.