Ich habe eine riesige (70 GB), einzeilige Textdatei und möchte eine Zeichenfolge (Token) ersetzen. Ich möchte das Token <unk>
durch ein anderes Dummy-Token ersetzen ( Handschuhproblem ).
Ich habe versucht sed
:
sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
aber die ausgabedatei corpus.txt.new
hat null bytes!
Ich habe auch versucht mit Perl:
perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
Aber ich habe einen Speicherfehler.
Bei kleineren Dateien funktionieren beide oben genannten Befehle.
Wie kann ich einen String ersetzen, der eine solche Datei ist? Dies ist eine verwandte Frage, aber keine der Antworten hat für mich funktioniert.
Bearbeiten : Wie wäre es, wenn Sie die Datei in Stücke von jeweils 10 GB (oder was auch immer) aufteilen und sed
auf jedes einzelne anwenden und sie dann zusammenführen cat
? Ist das sinnvoll? Gibt es eine elegantere Lösung?
quelle
split
mit der-b
Option "Blockdateigrößen in Byte definieren" verwenden. Jeweils nacheinander mit verarbeitensed
und wieder zusammenbauen. Es besteht die Gefahr, dass<unk>
in zwei Dateien aufgeteilt werden kann und nicht gefunden wird ...Antworten:
Die üblichen Textverarbeitungstools sind nicht für die Verarbeitung von Zeilen konzipiert, die nicht in den Arbeitsspeicher passen. Sie arbeiten in der Regel, indem sie einen Datensatz (eine Zeile) lesen, bearbeiten, das Ergebnis ausgeben und dann mit dem nächsten Datensatz (Zeile) fortfahren.
Wenn ein ASCII-Zeichen in der Datei häufig vorkommt und nicht in
<unk>
oder angezeigt wird<raw_unk>
, können Sie es als Datensatztrennzeichen verwenden. Da die meisten Tools keine benutzerdefinierten Datensatztrennzeichen zulassen, wechseln Sie zwischen diesem Zeichen und Zeilenumbrüchen.tr
verarbeitet Bytes, keine Zeilen, daher ist es egal, wie groß der Datensatz ist. Angenommen, das;
funktioniert:Sie können auch das erste Zeichen des gesuchten Texts ankern, vorausgesetzt, es wird im Suchtext nicht wiederholt und es wird häufig genug angezeigt. Wenn die Datei möglicherweise mit beginnt
unk>
, ändern Sie den Befehl sed insed '2,$ s/…
, um eine falsche Übereinstimmung zu vermeiden.Alternativ können Sie auch das letzte Zeichen verwenden.
Beachten Sie, dass diese Technik davon ausgeht, dass sed nahtlos mit einer Datei arbeitet, die nicht mit einem Zeilenumbruch endet, dh, dass sie die letzte Teilzeile verarbeitet, ohne sie abzuschneiden und ohne einen abschließenden Zeilenumbruch anzufügen. Es funktioniert mit GNU sed. Wenn Sie das letzte Zeichen der Datei als Datensatztrennzeichen auswählen können, vermeiden Sie Portabilitätsprobleme.
quelle
awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}'
Nein?-0
und dem Oktalwert eines$/
awk
vermeiden Sie es, den Stream zweimal zu leitentr
. Wäre es also noch langsamer?tr
ist sehr schnell und das Rohr kann sogar parallelisiert werden.Eine Möglichkeit für eine so große Datei ist Flex. Sei
unk.l
:Dann kompilieren und ausführen:
quelle
make
%option main
Hierfür gibt es Standardregeln. Anstelle von flex / cc können Sie eine als erste Zeile von unk.l und dann einfach hinzufügenmake unk
. Ich benutze mehr oder weniger reflexiv%option main 8bit fast
und habeexport CFLAGS='-march=native -pipe -Os'
in meinem.bashrc
.%option main
++make
optionalCFLAGS
gibt es einen sehr schönen trick !! Ist-march=native
das Standardverhalten?Sie haben also nicht genug physischen Speicher (RAM), um die gesamte Datei auf einmal zu speichern, aber auf einem 64-Bit-System haben Sie genug virtuellen Adressraum, um die gesamte Datei zuzuordnen. In solchen Fällen können virtuelle Zuordnungen als einfacher Hack nützlich sein.
Die notwendigen Operationen sind alle in Python enthalten. Es gibt einige ärgerliche Feinheiten, aber es wird vermieden, C-Code schreiben zu müssen. Insbesondere muss darauf geachtet werden, dass die Datei nicht in den Speicher kopiert wird, da dies den Punkt völlig zunichte macht. Auf der positiven Seite erhalten Sie eine kostenlose Fehlerberichterstattung (Python "Ausnahmen") :).
quelle
search
ein NUL-Zeichen enthalten sein kann. Und ich stelle fest, dass die andere C-Version hier keine NUL-Zeichen unterstütztreplace
.) Gerne können Sie die C-Version zu Vergleichszwecken ableiten. Denken Sie jedoch daran, dass meine Version grundlegende Fehlerberichte für die von ihr ausgeführten Vorgänge enthält. Die C-Version wäre zumindest ärgerlicher zu lesen , wenn Fehlerberichte enthalten wären .Das
replace
Paket mariadb-server / mysql-server enthält ein Hilfsprogramm. Es ersetzt einfache Zeichenketten (keine regulären Ausdrücke) undreplace
kümmert sich im Gegensatz zu grep / sed / awk nicht um\n
und\0
. Der Speicherverbrauch ist bei jeder Eingabedatei konstant (ca. 400 KB auf meinem Computer).Natürlich brauchen Sie keinen MySQL-Server, um es zu benutzen
replace
, es ist nur so in Fedora gepackt. Andere Distributionen / Betriebssysteme haben es möglicherweise separat verpackt.quelle
Ich denke, die C-Version könnte viel besser abschneiden:
EDIT: Geändert nach Vorschlägen aus den Kommentaren. Auch Fehler mit dem Muster behoben
<<unk>
.quelle
memcpy
Die Geschwindigkeit (dh der Speicherengpass) liegt bei einer aktuellen x86-CPU (z. B. Skylake) bei etwa 12 GB / Sekunde. Selbst mit stdio + Systemaufruf-Overhead für eine 30-MB-Datei, die sich im Festplatten-Cache befindet, würde ich vielleicht 1 GB / Sekunde für eine effiziente Implementierung erwarten. Haben Sie mit deaktivierter Optimierung kompiliert oder ist die einmalige Eingabe / Ausgabe wirklich so langsam?getchar_unlocked
/putchar_unlocked
könnte helfen, ist aber definitiv besser, in Blöcken von 128 KB zu lesen / schreiben (die Hälfte der L2-Cache-Größe auf den meisten x86-CPUs, sodass Sie meistens in L2 treffen, während Sie nach dem Lesen eine Schleife bilden)fix
zum Programm für"<<unk>"
funktioniert immer noch nicht, wenn daspattern
mit einer wiederholten Folge von Zeichen beginnt (dh es würde nicht funktionieren, wenn Sie versuchen, Aardvark durch Zebra zu ersetzen, und wenn Sie die Eingabe von Aardvak hatten, oder wenn Sie versuchen, ababc und zu ersetzen) hatte Eingabe von abababc). Im Allgemeinen können Sie nicht um die Anzahl der gelesenen Zeichen vorwärts gehen, es sei denn, Sie wissen, dass es keine Möglichkeit gibt, dass eine Übereinstimmung mit den gelesenen Zeichen beginnt.GNU
grep
kann Ihnen den Versatz von Übereinstimmungen in "binären" Dateien anzeigen, ohne dass Sie ganze Zeilen in den Speicher einlesen müssen. Sie können danndd
bis zu diesem Offset lesen, die Übereinstimmung überspringen und mit dem Kopieren aus der Datei fortfahren.Aus
dd
Gründen der Geschwindigkeit habe ich das in einen großen Lesevorgang mit Blockgröße 1048576 und einen kleineren Lesevorgang mit jeweils 1 Byte aufgeteilt, aber dieser Vorgang wird bei einer so großen Datei immer noch etwas langsam sein. Diegrep
Ausgabe ist zum Beispiel,13977:<unk>
und diese wird durch das Einlesen in Variablenoffset
und auf den Doppelpunkt aufgeteiltpattern
. Wir müssen nachverfolgen,pos
wie viele Bytes bereits aus der Datei kopiert wurden.quelle
Hier ist eine weitere einzelne UNIX-Befehlszeile, die möglicherweise eine bessere Leistung als andere Optionen erbringt, da Sie nach einer "Blockgröße" suchen können, die eine gute Leistung erbringt. Um robust zu sein, müssen Sie wissen, dass Sie in jedem X-Zeichen mindestens ein Leerzeichen haben, wobei X Ihre willkürliche "Blockgröße" ist. Im folgenden Beispiel habe ich eine "Blockgröße" von 1024 Zeichen gewählt.
Hier fängt fold bis zu 1024 Bytes ein, aber das -s stellt sicher, dass es in einem Leerzeichen bricht, wenn es seit der letzten Pause mindestens eins gibt.
Der sed Befehl liegt bei Ihnen und macht das, was Sie erwarten.
Dann "entfaltet" der Befehl tr die Datei und konvertiert die eingefügten Zeilenumbrüche zurück in nichts.
Sie sollten versuchen, größere Blöcke zu verwenden, um festzustellen, ob diese schneller sind. Anstelle von 1024 können Sie auch 10240 und 102400 und 1048576 für die Option -w verwenden.
Hier ist ein Beispiel für jeden Schritt, bei dem alle N in Kleinbuchstaben umgewandelt werden:
Sie müssen eine neue Zeile an das Ende der Datei anfügen, falls eine vorhanden ist, da diese mit dem Befehl tr entfernt wird.
quelle
Verwenden
perl
Eigene Puffer verwalten
Mit
IO::Handle
's können Siesetvbuf
die Standardpuffer verwalten, oder Sie können Ihre eigenen Puffer mitsysread
und verwaltensyswrite
. Überprüfen Sieperldoc -f sysread
undperldoc -f syswrite
für weitere Informationen, im Wesentlichen überspringen sie io gepuffert.Hier rollen wir unsere eigenen Puffer-E / A, aber wir machen es manuell und willkürlich auf 1024 Bytes. Wir öffnen auch die Datei für RW, damit wir alle auf einmal auf derselben FH ausführen können.
Wenn du diesen Weg gehen willst
<unk>
und<raw_unk>
sind die gleichen Byte - Größe.CHUNKSIZE
Grenze nicht überschreitet , wenn Sie mehr als 1 Byte ersetzen.quelle
<unk>
auf eine Grenze zwischen Stücken fällt?Sie könnten versuchen, bbe ( Binärblock-Editor ), ein "
sed
für Binärdateien".Ich hatte gute Erfolge bei der Verwendung einer 7-GB-Textdatei ohne
EOL
Zeichen, bei der mehrere Vorkommen einer Zeichenfolge durch eine Zeichenfolge unterschiedlicher Länge ersetzt wurden. Ohne Optimierungsversuch ergab sich ein durchschnittlicher Verarbeitungsdurchsatz von> 50 MB / s.quelle
Mit
perl
können Sie mit Datensätzen mit fester Länge arbeiten, z.Und ich hoffe, dass es nicht
<unk>
zwei dieser 100-MB-Datensätze gibt.quelle
while read -N 1000 chunk;
(das1000
ausgewählte als Beispiel). Die Lösung für die<unk>
Unterbrechung zwischen den Chunks sind zwei Durchgänge durch die Datei: der erste mit den 100-MB-Chunks und der zweite mit den 100-MB + 5-Byte-Chunks. Dies ist jedoch keine optimale Lösung für die 70-GB-Datei.<unk>
.<unk>
Vorkommen sind weit entfernt, wenn nicht, mit$/ = ">"
unds/<unk>\z/<raw_unk>/g
) korrekt ist.Hier ist ein kleines Go-Programm, das die Aufgabe ausführt (
unk.go
):Bauen Sie
go build unk.go
es einfach mit und führen Sie es als./unk <input >output
.BEARBEITEN:
Entschuldigung, ich habe nicht gelesen, dass alles in einer Zeile steht, also habe ich jetzt versucht, die Datei zeichenweise zu lesen.
EDIT II:
Wendet den gleichen Fix wie beim C-Programm an.
quelle
scanner.Split(bufio.ScanRunes)
macht die Magie.go doc bufio.MaxScanTokenSize
die Standardpuffergröße.C
Programm funktioniert dies nicht, wenn Sie Erdferkel durch Zebra durch eine Eingabe von Erdferkel ersetzen.Dies ist möglicherweise zu viel für eine 70-GB-Datei und ein einfaches Suchen und Ersetzen. Mit dem Hadoop MapReduce-Framework können Sie Ihr Problem jedoch sofort kostenlos lösen (wählen Sie die Option "Einzelner Knoten", wenn Sie es für die lokale Ausführung einrichten) zukünftig auf unendliche Kapazität skaliert, ohne dass Sie Ihren Code ändern müssen.
Das offizielle Tutorial unter https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html verwendet (extrem einfaches) Java, Sie finden jedoch Client-Bibliotheken für Perl oder Welche Sprache auch immer Sie verwenden möchten.
Wenn Sie also später feststellen, dass Sie komplexere Vorgänge mit 7000 GB Textdateien ausführen und dies 100 Mal pro Tag tun müssen, können Sie die Arbeitslast auf mehrere Knoten verteilen, die Sie bereitstellen oder die automatisch von einer Cloud für Sie bereitgestellt werden. Hadoop-Cluster.
quelle
Für alle vorherigen Vorschläge muss die gesamte Datei gelesen und die gesamte Datei geschrieben werden. Dies dauert nicht nur lange, sondern erfordert auch 70 GB freien Speicherplatz.
1) Wenn ich Dir richtig verstehe konkreten Fall wäre es akzeptabel, ersetzen <unk> mit einem anderen String der gleichen Länge?
2a) Gibt es mehrere Vorkommen? 2b) Wenn ja, wie viele?
Ich bin mir sicher, dass Sie dieses Problem bereits gelöst haben und ich würde gerne wissen, welche Lösung Sie verwendet haben.
Ich würde eine Lösung vorschlagen (höchstwahrscheinlich in C), die die BLÖCKE der Datei liest, die jeweils nach der Zeichenfolge suchen, wobei mögliche Blocküberschneidungen berücksichtigt werden. Einmal gefunden, ersetzen Sie die Zeichenkette mit der gleichen Länge alternativ und schreiben Sie nur diesen BLOCK. Fortsetzung für die bekannte Anzahl der Vorkommen oder bis zum Ende der Datei. Dies würde nur wenige Schreibvorgänge und höchstens das Doppelte erfordern (wenn jeder Vorgang auf zwei Blöcke aufgeteilt wurde). Dies würde KEINEN zusätzlichen Platz erfordern!
quelle
Wenn wir einen Mindestbetrag von
<unk>
(wie von Zipfs Gesetz erwartet) haben,quelle
sed
Liest eine Zeile gleichzeitig in den Speicher, unabhängig davon. Es wird nicht in der Lage sein, diese Linie zu passen.sed
nicht Eingabe / Ausgabe tun Pufferung , wenn dieses Flag verwenden. Ich kann nicht sehen, dass es Teilzeilen lesen wird.