![Große Datensätze/Absätze verarbeiten](https://rvso.com/image/178469/Gro%C3%9Fe%20Datens%C3%A4tze%2FAbs%C3%A4tze%20verarbeiten.png)
Ich habe eine große Textdatei (300 MB) mit Datensätzen mit \n\n
als Trennzeichen. Jede Zeile ist ein Feld und beginnt mit einer Nummer (dem Feldtag/-namen), gefolgt von einem TAB-Zeichen und dem Feldinhalt/-wert:
110 something from record 1, field 110
149 something else
111 any field could be repeatable
111 any number of times
120 another field
107 something from record 2, field 107
149 fields could be repeatable
149 a lot of times
149 I mean a LOT!
130 another field
107 something from record 3
149 something else
Jeder Datensatz sollte nicht größer als 100 KB sein.
Ich konnte einige problematische Datensätze (größer als das Limit) finden, indem ichEntfernen der Zeilenenden aus diesen Datensätzen/"Absätzen"Undseine Länge bekommen:
cat records.txt | awk ' /^$/ { print; } /./ { printf("%s ", $0); } ' | awk '{print length+1}' | sort -rn | grep -P "^\d{6,}$"
Ich versuche, eine Möglichkeit zu finden, diese ungültigen Datensätze zu verarbeiten, entweder:
- Entfernen von Datensätzen, die größer als das Limit sind.
- Entfernen aller Vorkommen eines bestimmten bekannten problematischen Tags (149 im obigen Beispiel). Die Hypothese, dass keine Datensätze über dem Grenzwert liegen, wenn alle Zeilen entfernt werden, die mit dem Feld 149 beginnen, ist akzeptabel.
Wahrscheinlich ist ein vollständiges Skript erforderlich, um genügend Vorkommen eines bestimmten Felds/Tags zu entfernen, damit es unter das Limit passt. Noch besser wäre es, zuerst die letzten zu entfernen.
Dies hängt mit einem alten Bibliothekar-Dateiformat namensISO 2709.
Antwort1
Wenn Sie nur die problematischen Datensätze überspringen möchten:
awk 'BEGIN { ORS=RS="\n\n" } length <= 100*1000' file
Dadurch wird jeder Datensatz gedruckt, der weniger als oder gleich 100.000 Zeichen enthält.
So löschen Sie die Felder, die mit einer bestimmten positiven Ganzzahl beginnen, wenn der Datensatz zu groß ist:
awk -v number=149 'BEGIN { ORS=RS="\n\n"; OFS=FS="\n" }
length <= 100*1000 { print; next }
{
# This is a too long record.
# Re-create it without any fields whose first tab-delimited
# sub-field is the number in the variable number.
# Split the record into an array of fields, a.
nf = split($0,a)
# Empty the record.
$0 = ""
# Go through the fields and add back the ones that we
# want to the output record.
for (i = 1; i <= nf; ++i) {
split(a[i],b,"\t")
if (b[1] != number) $(NF+1) = a[i]
}
# Print the output record.
print
}' file
Dadurch werden kurze Datensätze gedruckt, genau wie zuvor. Längere Datensätze werden von allen Feldern bereinigt, deren erstes tabulatorgetrenntes Unterfeld die Zahl ist number
(hier in der Befehlszeile als 149 angegeben).
Bei großen Datensätzen wird der Datensatz ohne die Felder, die wir nicht wollen, neu erstellt. Die innere Schleife erstellt den Ausgabedatensatz neu, indem sie die Felder in Tabulatoren aufteilt und diejenigen anhängt, deren erstes tabulatorgetrenntes Unterfeld nicht ist number
:
for (i = 1; i <= nf; ++i) {
split(a[i],b,"\t")
if (b[1] != number) $(NF+1) = a[i]
}
Da die POSIX-Spezifikation für awk
nicht spezifiziert, was passiert, wenn Sie einen mehrstelligen Wert in haben RS
(die meisten Implementierungen behandeln ihn als regulären Ausdruck), können Sie bei Verwendung Ihrer streng konformen Implementierung RS=""; ORS="\n\n"
anstelle verwenden . Wenn Sie dies tun, beachten Sie, dass mehrere leere Zeilen in den Daten leere Datensätze nicht mehr abgrenzen würden.ORS=RS="\n\n"
awk
Antwort2
Ein anderer awk
Ansatz:
awk -v lim=99999 'BEGIN{RS=""; ORS="\n\n"}\
{while (length()>=lim) {if (!sub(/\n149\t[^\n]*/,"")) break;}} length()<lim' file
Dadurch werden nach und nach Zeilen entfernt, beginnend mit , 149
wenn die Datensatzlänge über dem in der Variablen angegebenen Grenzwert liegt lim
, indem sie durch „nichts“ ersetzt werden, bis entweder der Grenzwert eingehalten wurde oder keine weitere Reduzierung möglich ist (erkennbar daran, dass die Anzahl der tatsächlichen Ersetzungen 0 ist). Es werden dann nur noch Datensätze gedruckt, deren endgültige Länge kleiner als der Grenzwert ist.
Nachteil:Dabei werden die Zeilen ab der ersten entfernt 149
. Wenn diese also einzelne Elemente eines zusammenhängenden Textes darstellen, wird dieser Text unverständlicher.
Notiz:Angeben RS=""
statt explizit RS="\n\n"
ist dastragbarVerwendung awk
im "Absatzmodus", da das Verhalten von Mehrfachzeichen RS
nicht durch die POSIX-Spezifikation definiert ist. Wenn jedochleerDatensätze in Ihrer Datei werden ignoriert und erscheinen daher nicht in der Ausgabe. Wenn dies nicht das ist, was Sie möchten, müssen Sie stattdessen möglicherweise die explizite Notation awk
verwenden – die meisten Implementierungen behandeln sie als regulären Ausdruck und tun, was man „naiv“ erwarten würde.RS="\n\n"
awk
Antwort3
\n\n
Wenn Sie als Datensatztrennzeichen Folgendes haben , denken Sie an Perl und den Absatzmodus (von man perlrun
):
-0[octal/hexadecimal]
specifies the input record separator ($/) as an octal or hexadecimal number.
[...]
The special value 00 will cause Perl to slurp files in paragraph mode.
Damit können Sie Folgendes tun:
Entfernen Sie alle Datensätze, die länger als 100.000 sindFiguren(Beachten Sie, dass dies je nach Kodierung Ihrer Datei möglicherweise nicht dasselbe wie Bytes ist):
perl -00 -ne 'print unless length()>100000' file
Kürzen Sie alle Datensätze, die länger als 100.000 Zeichen sind, indem Sie alle Zeichen nach den ersten 100.000 entfernen:
perl -00 -lne 'print substr($_,0,100000)' file
Entfernen Sie Zeilen, die mit folgendem beginnen
149
:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g;' file
Entfernen Sie Zeilen, die mit
149
„aber“ beginnen, nur wenn dieser Datensatz länger als 100.000 Zeichen ist:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g if length()>100000; ' file
Wenn ein Datensatz länger als 100.000 Zeichen ist, entfernen Sie Zeilen, die mit beginnen,
149
bis entweder der Datensatz weniger als 100.000 Zeichen hat oder keine Zeilen mit 149 mehr vorhanden sind:perl -00 -pe 'while(length()>100000 && /(^|\n)149\s/){s/(^|\n)149\s+[^\n]+//}' file
Wenn ein Datensatz länger als 100000 Zeichen ist, entfernen Sie Zeilen, die mit beginnen,
149
bis der Datensatz entweder weniger als 100000 Zeichen hat oder es keine Zeilen mehr mit 149 gibt, und wenn esTrotzdemlänger als 100.000 Zeichen, nur die ersten 100.000 drucken:perl -00 -lne 'while(length()>100000 && /(^|\n)149\s/){ s/(^|\n)149\s+[^\n]+// } print substr($_,0,100000)' file
Zum Schluss wie oben, aber entfernen Sie ganze Zeilen, nicht nur Zeichen, bis Sie die richtige Größe erreicht haben, sodass Ihre Datensätze nicht abgeschnitten sind:
perl -00 -ne 'while(length()>100000 && /(^|\n)149\s/){ s/(^|\n)149\s+[^\n]+// } map{ $out.="$_\n" if length($out . "\n$_")<=100000 }split(/\n/); print "$out\n"; $out="";' file
Antwort4
Es könnte wahrscheinlich eleganter sein, aber hier ist eine Lösung:
cat records.txt | awk -v RS='' '{if (length>99999) {gsub(/\n149\t[^\n]*\n/,"\n");print $0"\n"} else {print $0"\n"} }'
Ich bin mir der nutzlosen Verwendung von Katze bewusst, ich glaubeist klarer der Fluss von links nach rechts.
Dabei ist 99999 die Schwellengröße und 149 der Zeilenanfang (Feldname), der in diesem Fall entfernt werden soll.
Ich verwende ein nicht gieriges Mittel, \n149\t[^\n]*\n/
um nur das zu entfernen, was sein würde ^149\t.*$
.
gsub
ersetzt das Muster durch die angegebene Zeichenfolge und gibt die Anzahl der vorgenommenen Ersetzungen/Ersetzungen zurück.
Die Inspiration stammte vondiese Antwort.