Teilen Sie große Dateien in Blöcke auf, ohne den Eintrag zu teilen


8

Ich habe eine ziemlich große .msg-Datei im UIEE-Format formatiert.

$ wc -l big_db.msg
8726593 big_db.msg

Im Wesentlichen besteht die Datei aus Einträgen unterschiedlicher Länge, die ungefähr so ​​aussehen:

UR|1
AA|Condon, Richard
TI|Prizzi's Family
CN|Collectable- Good/Good
MT|FICTION
PU|G.P. Putnam & Sons
DP|1986
ED|First Printing.
BD|Hard Cover
NT|0399132104
KE|MAFIA
KE|FICTION
PR|44.9
XA|4
XB|1
XC|BO
XD|S

UR|10
AA|Gariepy, Henry
TI|Portraits of Perseverance
CN|Good/No Jacket
MT|SOLD
PU|Victor Books
DP|1989
BD|Mass Market Paperback
NT|1989 tpb g 100 meditations from the Book of Job "This book...help you
NT| persevere through the struggles of your life..."
KE|Bible
KE|religion
KE|Job
KE|meditations
PR|28.4
XA|4
XB|5
XC|BO
XD|S

Dies ist ein Beispiel für zwei Einträge, die durch eine Leerzeile getrennt sind. Ich möchte diese große Datei in kleinere Dateien aufteilen, ohne einen Eintrag in zwei Dateien aufzuteilen.

Jeder einzelne Eintrag wird durch eine neue Zeile (eine vollständig leere Zeile) in der Datei getrennt. Ich möchte diese 8,7-Millionen-Zeilendatei in 15 Dateien aufteilen. Ich verstehe, dass splites solche Tools gibt, aber ich bin mir nicht ganz sicher, wie ich die Datei aufteilen soll, sondern nur in einer neuen Zeile, damit ein einzelner Eintrag nicht in mehrere Dateien aufgeteilt wird.


csplitexistiert auch.
Mikesserv

Können Sie temporäre Dateien erstellen?
Braiam

@Braiam, nicht sicher, was du meinst, aber ich denke schon. Ich habe vollen Zugriff über das Dateisystem.
user2036066

er bedeutet, Dateien zu erstellen, die vorübergehend für den Prozess verwendet werden
polym

1
Warum genau 15 Dateien, wenn ich fragen darf? Sind die Präfixe vor dem Rohr |(wie UR, AA, TI) relevant für die Zählung von Dateien, auch exakt das gleiche sein?
Polym

Antworten:


2

Hier ist eine Lösung, die funktionieren könnte:

seq 1 $(((lines=$(wc -l </tmp/file))/16+1)) $lines |
sed 'N;s|\(.*\)\(\n\)\(.*\)|\1d;\1,\3w /tmp/uptoline\3\2\3|;P;$d;D' |
sed -ne :nl -ne '/\n$/!{N;bnl}' -nf - /tmp/file

Es funktioniert, indem der erste seddas sedSkript des zweiten schreiben kann . Die zweite sederste sammelt alle Eingabezeilen, bis sie auf eine leere Zeile stößt. Anschließend werden alle Ausgabezeilen in eine Datei geschrieben. Der erste sedschreibt ein Skript für den zweiten aus und weist ihn an, wo seine Ausgabe geschrieben werden soll. In meinem Testfall sah dieses Skript folgendermaßen aus:

1d;1,377w /tmp/uptoline377
377d;377,753w /tmp/uptoline753
753d;753,1129w /tmp/uptoline1129
1129d;1129,1505w /tmp/uptoline1505
1505d;1505,1881w /tmp/uptoline1881
1881d;1881,2257w /tmp/uptoline2257
2257d;2257,2633w /tmp/uptoline2633
2633d;2633,3009w /tmp/uptoline3009
3009d;3009,3385w /tmp/uptoline3385
3385d;3385,3761w /tmp/uptoline3761
3761d;3761,4137w /tmp/uptoline4137
4137d;4137,4513w /tmp/uptoline4513
4513d;4513,4889w /tmp/uptoline4889
4889d;4889,5265w /tmp/uptoline5265
5265d;5265,5641w /tmp/uptoline5641

Ich habe es so getestet:

printf '%s\nand\nmore\nlines\nhere\n\n' $(seq 1000) >/tmp/file

Dies lieferte mir eine Datei mit 6000 Zeilen, die so aussah:

<iteration#>
and
more
lines
here
#blank

... 1000 mal wiederholt.

Nach dem Ausführen des obigen Skripts:

set -- /tmp/uptoline*
echo $# total splitfiles
for splitfile do
    echo $splitfile
    wc -l <$splitfile
    tail -n6 $splitfile
done    

AUSGABE

15 total splitfiles
/tmp/uptoline1129
378
188
and
more
lines
here

/tmp/uptoline1505
372
250
and
more
lines
here

/tmp/uptoline1881
378
313
and
more
lines
here

/tmp/uptoline2257
378
376
and
more
lines
here

/tmp/uptoline2633
372
438
and
more
lines
here

/tmp/uptoline3009
378
501
and
more
lines
here

/tmp/uptoline3385
378
564
and
more
lines
here

/tmp/uptoline3761
372
626
and
more
lines
here

/tmp/uptoline377
372
62
and
more
lines
here

/tmp/uptoline4137
378
689
and
more
lines
here

/tmp/uptoline4513
378
752
and
more
lines
here

/tmp/uptoline4889
372
814
and
more
lines
here

/tmp/uptoline5265
378
877
and
more
lines
here

/tmp/uptoline5641
378
940
and
more
lines
here

/tmp/uptoline753
378
125
and
more
lines
here

3

Mit dem Vorschlag von csplit:

Aufteilung nach Zeilennummern

$ csplit file.txt <num lines> "{repetitions}"

Beispiel

Angenommen, ich habe eine Datei mit 1000 Zeilen.

$ seq 1000 > file.txt

$ csplit file.txt 100 "{8}"
288
400
400
400
400
400
400
400
400
405

führt zu Dateien wie folgt:

$ wc -l xx*
  99 xx00
 100 xx01
 100 xx02
 100 xx03
 100 xx04
 100 xx05
 100 xx06
 100 xx07
 100 xx08
 101 xx09
   1 xx10
1001 total

Sie können die statische Einschränkung umgehen, dass die Anzahl der Wiederholungen angegeben werden muss, indem Sie die Anzahl anhand der Anzahl der Zeilen in Ihrer bestimmten Datei im Voraus vorberechnen.

$ lines=100
$ echo $lines 
100

$ rep=$(( ($(wc -l file.txt | cut -d" " -f1) / $lines) -2 ))
$ echo $rep
8

$ csplit file.txt 100 "{$rep}"
288
400
400
400
400
400
400
400
400
405

Aufteilung anhand von Leerzeilen

Wenn Sie andererseits eine Datei einfach in leere Zeilen aufteilen möchten, die in der Datei enthalten sind, können Sie diese Version von verwenden split:

$ csplit file2.txt '/^$/' "{*}"

Beispiel

Angenommen, ich habe oben 4 leere Zeilen hinzugefügt file.txtund die Datei erstellt file2.txt. Sie können sehen, dass sie wie folgt manuell hinzugefügt wurden:

$ grep -A1 -B1 "^$" file2.txt
20

21
--
72

73
--
112

113
--
178

179

Das Obige zeigt, dass ich sie zwischen den entsprechenden Nummern in meiner Beispieldatei hinzugefügt habe. Wenn ich jetzt den csplitBefehl ausführe :

$ csplit file2.txt '/^$/' "{*}"
51
157
134
265
3290

Sie können sehen, dass ich jetzt 4 Dateien habe, die basierend auf der Leerzeile aufgeteilt wurden:

$ grep -A1 -B1 '^$' xx0*
xx01:
xx01-21
--
xx02:
xx02-73
--
xx03:
xx03-113
--
xx04:
xx04-179

Verweise


Ich habe das OP mit meinem Versuch, dies zu verwenden, bearbeitet und konnte es nicht zum Laufen bringen.
user2036066

Die Datei wurde nicht in eine neue, leere Zeile aufgeteilt, was ich versucht habe zu erreichen.
user2036066

@ user2036066 - Sie möchten die Datei in 15 Dateiblöcke aufteilen, um sicherzustellen, dass keine Teilzeile oder etwas anderes aufgeteilt wird?
slm

@ user2036066 - Warten Sie, bis die Datei 14-15 vollständig leere Zeilen enthält, auf die Sie teilen möchten.
slm

Bearbeitet die Operation erneut mit mehr Kontext @slm
user2036066

3

Wenn Sie sich nicht um die Reihenfolge der Aufzeichnungen kümmern, können Sie Folgendes tun:

gawk -vRS= '{printf "%s", $0 RT > "file.out." (NR-1)%15}' file.in

Andernfalls müssen Sie zuerst die Anzahl der Datensätze ermitteln, um zu wissen, wie viele in jede Ausgabedatei eingefügt werden sollen:

gawk -vRS= -v "n=$(gawk -vRS= 'END {print NR}' file.in)" '
  {printf "%s", $0 RT > "file.out." int((NR-1)*15/n)}' file.in

Die Verwendung von awk zum
Teilen

Was sind file.inund file.out?
Mikeserv

1

Wenn Sie nur am Ende einer Zeile teilen möchten, sollten Sie dies mit der -lOption für tun können split.

Wenn Sie auf einer leeren Zeile ( \n\n) teilen möchten, würde ich dies in ksh tun. Ich habe es nicht getestet und es ist wahrscheinlich nicht ideal, aber etwas in dieser Richtung würde funktionieren:

filenum=0
counter=0
limit=580000

while read LINE
do
  counter=counter+1

  if (( counter >= limit ))
  then
    if [[ $LINE == "" ]]
    then
      filenum=filenum+1
      counter=0
    fi
  fi

  echo $LINE >>big_db$filenum.msg
done <big_db.msg

1
Es ist möglich, dass ich falsch verstanden habe, aber op fragt, wie man sich aufteilt, \n\ndenke ich.
Mikesserv

Das hilft mir nicht wirklich, da dadurch die Datei während des Eintrags immer noch aufgeteilt wird. Ich brauche es, damit die Datei nur in eine leere Zeile aufgeteilt wird.
user2036066

Ja, ich habe falsch verstanden, sorry. Es ist möglicherweise nicht der beste Weg, ich würde einfach die Originaldatei in eine Schleife mit einem Zähler für die Anzahl der übergebenen Zeilen einlesen und sobald Sie die Zahl erreicht haben, die Sie teilen möchten, beginnen Sie mit der Ausgabe in eine neue Datei bei der nächsten Leerzeile.
Hornj

Ich versuche gerade, dieses Skript zu testen.
user2036066

1
Ich denke, OP fragt nicht, wie man sich aufteilt \n\n, sondern nicht mitten in einer Linie. Er nennt eine neue Zeile eine leere Zeile.
Polym

0

Versuchen awk

awk 'BEGIN{RS="\n\n"}{print $0 > FILENAME"."FNR}' big_db.msg

Versuchen Sie diese Lösung jetzt
user2036066

2
Diese Lösung erstellt für jeden Eintrag eine neue Datei, was ich überhaupt nicht möchte.
user2036066

0

Wenn Sie sich nicht um die Reihenfolge der Datensätze kümmern, sondern eine bestimmte Anzahl von Ausgabedateien erhalten möchten, ist Stephanes Antwort der richtige Weg. Ich habe jedoch das Gefühl, dass es Ihnen wichtiger sein könnte, eine Größe anzugeben, die jede Ausgabedatei nicht überschreiten sollte. Das macht es tatsächlich einfacher, weil Sie Ihre Eingabedatei lesen und Datensätze sammeln können, bis Sie diese Größe erreicht haben, und dann eine neue Ausgabedatei starten können. Wenn das für Sie funktioniert, können die meisten Programmiersprachen Ihre Aufgabe mit einem kurzen Skript erledigen. Hier ist eine awk-Implementierung:

BEGIN {
    RS = "\n\n"
    ORS = "\n\n"
    maxlen = (maxlen == 0 ? 500000 : maxlen)
    oi = 1
}

{
    reclen = length($0) + 2
    if (n + reclen > maxlen) {
        oi++
        n = 0
    }
    n += reclen
    print $0 > FILENAME"."oi
}

Fügen Sie dies beispielsweise in eine Datei ein program.awkund führen Sie es so aus, awk -v maxlen=10000 -f program.awk big_db.msgdass der Wert von maxlendie meisten Bytes ist, die Sie in einer Datei benötigen. Standardmäßig werden 500 KB verwendet.

Wenn Sie eine festgelegte Anzahl von Dateien erhalten möchten, ist es wahrscheinlich am einfachsten, die Größe Ihrer Eingabedatei durch die Anzahl der gewünschten Dateien zu teilen und dann ein bisschen zu dieser Anzahl hinzuzufügen, um sie zu erhalten maxlen. Um beispielsweise 15 Dateien aus Ihren 8726593-Bytes herauszuholen, dividieren Sie durch 15, um 581773 zu erhalten, und fügen Sie einige hinzu. Geben Sie also vielleicht maxlen=590000oder maxlen=600000. Wenn Sie dies wiederholt tun möchten, können Sie das Programm dafür konfigurieren.

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.