Ersetzen Sie die Zeichenfolge in einer riesigen (70 GB), einzeiligen Textdatei

126

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.newhat 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 sedauf jedes einzelne anwenden und sie dann zusammenführen cat? Ist das sinnvoll? Gibt es eine elegantere Lösung?

Christos Baziotis
quelle
Können Sie, wie @Gilles bemerkte, ein sich wiederholendes Zeichen erkennen, das als benutzerdefiniertes Trennzeichen in Ihrer einzelnen großen Zeile dienen könnte?
RomanPerekhrest
Ich denke, dass ein Tool, das nur suchen und ersetzen kann, aber keine komplexeren regulären Ausdrücke, schneller wäre. Es würde auch nicht von Vorteil sein, wenn Sie eine Zeile gleichzeitig ausführen, sodass Sie an dieser Datei nicht ersticken würden. Leider habe ich keine Ahnung von der Existenz eines solchen Tools, obwohl es nicht schwer zu schreiben wäre. Wenn es ein Einzelfall ist, ist es wahrscheinlich am einfachsten, in Zeilenumbrüchen wie in einer der Antworten zu ersetzen.
ctrl-alt-delor
Enthält Ihre Datei etwas anderes als ASCII? In diesem Fall könnte die gesamte Unicode-Behandlung weggelassen und unformatierte Bytes verarbeitet werden.
Patrick Bucher
Ich stimme @PatrickButcher zu. Sehen Sie sich ein größeres Bild an. Wofür soll diese Datei verwendet werden, außer dass dieser Text sofort ersetzt werden muss? Wenn es sich um ein Protokoll handelt, kann niemand effektiv damit arbeiten. Wenn es sich um eine Datendatei handelt, die von einer App verwendet wird, sollte diese App die Verantwortung für die Verwaltung der Daten in dieser Datei tragen.
Thomas Carlisle
2
Sie können splitmit der -bOption "Blockdateigrößen in Byte definieren" verwenden. Jeweils nacheinander mit verarbeiten sedund wieder zusammenbauen. Es besteht die Gefahr, dass <unk>in zwei Dateien aufgeteilt werden kann und nicht gefunden wird ...
Vladislavs Dovgalecs

Antworten:

106

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. trverarbeitet Bytes, keine Zeilen, daher ist es egal, wie groß der Datensatz ist. Angenommen, das ;funktioniert:

<corpus.txt tr '\n;' ';\n' |
sed 's/<unk>/<raw_unk>/g' |
tr '\n;' ';\n' >corpus.txt.new

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 in sed '2,$ s/…, um eine falsche Übereinstimmung zu vermeiden.

<corpus.txt tr '\n<' '<\n' |
sed 's/^unk>/raw_unk>/g' |
tr '\n<' '<\n' >corpus.txt.new

Alternativ können Sie auch das letzte Zeichen verwenden.

<corpus.txt tr '\n>' '>\n' |
sed 's/<unk$/<raw_unk/g' |
tr '\n>' '>\n' >corpus.txt.new

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.

Gilles
quelle
8
Ich habe keine solche Datei zum Testen, aber in Awk können Sie das "Record Separator" und das "Output Record Separator" angeben. Angenommen, Sie haben ein ordentliches Komma in Ihrer Datei, ist es möglich, dass Sie dieses Problem lösen: awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}' Nein?
Wildcard
4
@Wildcard Ja, das ist eine andere Lösung. Awk ist in der Regel langsamer als sed, daher biete ich es nicht als bevorzugte Lösung für eine große Datei an.
Gilles
Sie können das Datensatztrennzeichen in Perl mit der Befehlszeilenoption -0und dem Oktalwert eines $/
Zeichens festlegen
@ Gilles: Aber mit awkvermeiden Sie es, den Stream zweimal zu leiten tr. Wäre es also noch langsamer?
user285259
2
@ user285259 Normalerweise nicht. trist sehr schnell und das Rohr kann sogar parallelisiert werden.
Gilles
110

Eine Möglichkeit für eine so große Datei ist Flex. Sei unk.l:

%%
\<unk\>     printf("<raw_unk>");  
%%

Dann kompilieren und ausführen:

$ flex -o unk.c  unk.l
$ cc -o unk -O2 unk.c -lfl
$ unk < corpus.txt > corpus.txt.new
Joao
quelle
5
make%option mainHierfür gibt es Standardregeln. Anstelle von flex / cc können Sie eine als erste Zeile von unk.l und dann einfach hinzufügen make unk. Ich benutze mehr oder weniger reflexiv %option main 8bit fastund habe export CFLAGS='-march=native -pipe -Os'in meinem .bashrc.
30.
1
@undercat: Wenn es nicht vom Thema abweicht, könnte ich Ihnen eine Reihe von Front-End-Anwendungen ohne Compiler zeigen, von der Lösung des Wasserstandsproblems bis zur speziellen Analyse von Eingaben. Es ist erstaunlich, was man damit
anfangen
@jthill, danke: %option main++ makeoptional CFLAGSgibt es einen sehr schönen trick !! Ist -march=nativedas Standardverhalten?
Joao
1
@jamesqf wie du gesagt hast - es wird schwer sein, das zu einer thematischen Frage zu machen - aber ich würde es auch gerne sehen
Steven Penny
1
@jamesqf Ein Professor von mir an der Uni hat Flex verwendet, um ein Werkzeug zu bauen, das Stofftypen für eine Fabrik erkennt! Wie wäre es mit einer Frage wie: "flex scheint ein sehr mächtiges Werkzeug zu sein, aber ich werde wahrscheinlich keine Compiler / Parser schreiben - gibt es andere Anwendungsfälle für flex?"
Paul Evans
40

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

#!/usr/bin/python3
# This script takes input from stdin
# (but it must be a regular file, to support mapping it),
# and writes the result to stdout.

search = b'<unk>'
replace = b'<raw_unk>'


import sys
import os
import mmap

# sys.stdout requires str, but we want to write bytes
out_bytes = sys.stdout.buffer

mem = mmap.mmap(sys.stdin.fileno(), 0, access=mmap.ACCESS_READ)
i = mem.find(search)
if i < 0:
    sys.exit("Search string not found")

# mmap object subscripts to bytes (making a copy)
# memoryview object subscripts to a memoryview object
# (it implements the buffer protocol).
view = memoryview(mem)

out_bytes.write(view[:i])
out_bytes.write(replace)
out_bytes.write(view[i+len(search):])
sourcejedi
quelle
Wenn mein System ungefähr 4 GB freien Speicher hat, bedeutet mem = mmap.mmap (sys.stdin.fileno (), 0, access = mmap.ACCESS_READ), dass die Daten in diesem Bereich abgelegt werden? Oder wäre es viel niedriger (1GB?)>
Rahul
1
@Rahul "Sie haben also nicht genug RAM, aber auf einem 64-Bit-System haben Sie genug virtuellen Adressraum, um die gesamte Datei zuzuordnen." Es wird bei Bedarf im physischen Arbeitsspeicher ein- und ausgelagert (oder es fehlt). Dieses Programm sollte funktionieren, ohne dass eine große Menge an physischem RAM erforderlich ist. 64-Bit-Systeme haben viel mehr virtuellen Adressraum als der maximale physische RAM. Außerdem hat jeder ausgeführte Prozess einen eigenen virtuellen Adressraum. Dies bedeutet, dass dem System insgesamt der virtuelle Adressraum ausgeht. Dies ist kein gültiges Konzept.
Sourcejedi
4
@ Rahul yep! python mmap.mmap () ist ein ziemlich dünner Wrapper um die C-Funktion mmap (). Und mmap () ist derselbe Mechanismus, mit dem ausführbare Dateien und Code aus gemeinsam genutzten Bibliotheken ausgeführt werden.
Sourcejedi
2
@jamesqf Ich könnte mich irren, aber ich denke, es ist nur eine persönliche Entscheidung. Da die Performance-Einbußen vernachlässigbar wären (weil die aktuelle Funktion, wie er sagte, die c-Funktion aufruft), ist der Overhead-Verlust sehr gering, da keine anderen Dinge dazwischen passieren. C wäre besser gewesen, aber diese Lösung zielte nicht auf eine Optimierung ab, nur um das größere und schwierige Problem mit 70 GB zu lösen.
Rahul
1
Im Allgemeinen ist das Schreiben in Python kompakter. In diesem Fall stellte sich heraus, dass die Python-Version einige Details enthält, und die C-Version war möglicherweise besser zu schreiben. (Obwohl es nicht so einfach ist, wenn searchein NUL-Zeichen enthalten sein kann. Und ich stelle fest, dass die andere C-Version hier keine NUL-Zeichen unterstützt replace.) 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 .
Sourcejedi
16

Das replacePaket mariadb-server / mysql-server enthält ein Hilfsprogramm. Es ersetzt einfache Zeichenketten (keine regulären Ausdrücke) und replacekümmert sich im Gegensatz zu grep / sed / awk nicht um \nund \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.

legolegs
quelle
16

Ich denke, die C-Version könnte viel besser abschneiden:

#include <stdio.h>
#include <string.h>

#define PAT_LEN 5

int main()
{
    /* note this is not a general solution. In particular the pattern
     * must not have a repeated sequence at the start, so <unk> is fine
     * but aardvark is not, because it starts with "a" repeated, and ababc
     * is not because it starts with "ab" repeated. */
    char pattern[] = "<unk>";          /* set PAT_LEN to length of this */
    char replacement[] = "<raw_unk>"; 
    int c;
    int i, j;

    for (i = 0; (c = getchar()) != EOF;) {
        if (c == pattern[i]) {
            i++;
            if (i == PAT_LEN) {
                printf("%s", replacement);
                i = 0;
            }
        } else {
            if (i > 0) {
                for (j = 0; j < i; j++) {
                    putchar(pattern[j]);
                }
                i = 0;
            }
            if (c == pattern[0]) {
                i = 1;
            } else {
                putchar(c);
            }
        }
    }
    /* TODO: fix up end of file if it ends with a part of pattern */
    return 0;
}

EDIT: Geändert nach Vorschlägen aus den Kommentaren. Auch Fehler mit dem Muster behoben <<unk>.

Patrick Bucher
quelle
2
Sie können (Muster [j]) anstelle von (Buf [j]) drucken (sie sind an dieser Stelle gleich, sodass Sie keinen Puffer benötigen
RIAD
3
Auch Code funktioniert nicht für die Zeichenfolge "<< unk
RiaD
10
30 MB in 0,3 Sekunden? Das sind nur 90 MB / Sekunde. memcpyDie 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_unlockedkö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)
Peter Cordes
2
von oben auf den Kopf, getchar und putchar ist langsam.
Rui F Ribeiro
3
Das fixzum Programm für "<<unk>"funktioniert immer noch nicht, wenn das patternmit 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.
Ikarus
14

GNU grepkann Ihnen den Versatz von Übereinstimmungen in "binären" Dateien anzeigen, ohne dass Sie ganze Zeilen in den Speicher einlesen müssen. Sie können dann ddbis zu diesem Offset lesen, die Übereinstimmung überspringen und mit dem Kopieren aus der Datei fortfahren.

file=...
newfile=...
replace='<raw_unk>'
grep -o -b -a -F '<unk>' <"$file" |
(   pos=0
    while IFS=$IFS: read offset pattern
    do size=${#pattern}
       let skip=offset-pos
       let big=skip/1048576
       let skip=skip-big*1048576
       dd bs=1048576 count=$big <&3
       dd bs=1 count=$skip <&3
       dd bs=1 count=$size of=/dev/null <&3
       printf "%s" "$replace"
       let pos=offset+size
    done
    cat <&3
) 3<"$file" >"$newfile"

Aus ddGrü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. Die grepAusgabe ist zum Beispiel, 13977:<unk>und diese wird durch das Einlesen in Variablen offsetund auf den Doppelpunkt aufgeteilt pattern. Wir müssen nachverfolgen, poswie viele Bytes bereits aus der Datei kopiert wurden.

meuh
quelle
11

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.

fold -w 1024 -s corpus.txt | sed 's/<unk>/<raw_unk>/g' | tr '/n' '/0'

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:

[root@alpha ~]# cat mailtest.txt
test XJS C4JD QADN1 NSBN3 2IDNEN GTUBE STANDARD ANTI UBE-TEST EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt
test XJS C4JD QADN1
NSBN3 2IDNEN GTUBE
STANDARD ANTI
UBE-TEST
EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g'
test XJS C4JD QADn1
nSBn3 2IDnEn GTUBE
STAnDARD AnTI
UBE-TEST
EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g' | tr '\n' '\0'
test XJS C4JD QADn1 nSBn3 2IDnEn GTUBE STAnDARD AnTI UBE-TEST EMAIL*C.34X test

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.

alfreema
quelle
1
Wie stellen Sie sicher, dass Sie das Muster nicht brechen, wenn nicht genügend Leerzeichen verfügbar sind?
Rackandboneman
1
Wie bereits erwähnt, muss mindestens ein Leerzeichen pro X-Zeichen eingegeben werden, um die Robustheit zu gewährleisten. Sie können diese Analyse mit jeder Blockgröße durchführen, die Sie auswählen: fold -w X mailtest.txt | grep -v "" | wc -l Die Zahl, die zurückgegeben wird, ist die Anzahl der gefalteten Linien mit möglichen Kantenfällen. Wenn es Null ist, funktioniert die Lösung garantiert.
Alfreema
10

Verwenden perl

Eigene Puffer verwalten

Mit IO::Handle's können Sie setvbufdie Standardpuffer verwalten, oder Sie können Ihre eigenen Puffer mit sysreadund verwalten syswrite. Überprüfen Sie perldoc -f sysreadund perldoc -f syswritefü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.

use strict;
use warnings;
use Fcntl qw(:flock O_RDWR);
use autodie;
use bytes;

use constant CHUNK_SIZE => 1024 * 32;

sysopen my $fh, 'file', O_RDWR;
flock($fh, LOCK_EX);

my $chunk = 1;
while ( sysread $fh, my $bytes, CHUNK_SIZE * $chunk ) {
  if ( $bytes =~ s/<unk>/<raw_unk>/g ) {
    seek( $fh, ($chunk-1)* CHUNK_SIZE, 0 );
    syswrite( $fh, $bytes, 1024);
    seek( $fh, $chunk * CHUNK_SIZE, 0 );
  }
  $chunk++;
}

Wenn du diesen Weg gehen willst

  1. Achten Sie darauf , <unk>und <raw_unk>sind die gleichen Byte - Größe.
  2. Sie können sicherstellen, dass unsere gepufferte Methode die CHUNKSIZEGrenze nicht überschreitet , wenn Sie mehr als 1 Byte ersetzen.
Evan Carroll
quelle
2
Was ist, wenn es <unk>auf eine Grenze zwischen Stücken fällt?
Liori
8

Sie könnten versuchen, bbe ( Binärblock-Editor ), ein " sedfür Binärdateien".

Ich hatte gute Erfolge bei der Verwendung einer 7-GB-Textdatei ohne EOLZeichen, 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.

ovirt
quelle
5

Mit perlkönnen Sie mit Datensätzen mit fester Länge arbeiten, z.

perl -pe 'BEGIN{$/=\1e8}
          s/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

Und ich hoffe, dass es nicht <unk>zwei dieser 100-MB-Datensätze gibt.

Stéphane Chazelas
quelle
Ich habe auch über diese Methode nachgedacht, aber das while read -N 1000 chunk;(das 1000ausgewä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.
MiniMax
3
Sie brauchen nicht einmal zwei Pässe. Block A lesen. Wenn nicht EOF, Block B lesen. Suchen / Ersetzen in A + B. A: = B. Schleife. Die Komplexität stellt sicher, dass Sie nicht innerhalb des Ersatzes ersetzen.
Roaima
@MiniMax, dieser zweite Durchlauf würde nicht unbedingt helfen, da der erste Durchlauf 5 Bytes für jedes Auftreten von hinzugefügt hätte <unk>.
Stéphane Chazelas
1
@roaima, ja das wäre eine viel aufwendigere Lösung. Hier handelt es sich um einen einfachen Ansatz, der nur mit hoher Wahrscheinlichkeit (vorausgesetzt, die <unk>Vorkommen sind weit entfernt, wenn nicht, mit $/ = ">"und s/<unk>\z/<raw_unk>/g) korrekt ist.
Stéphane Chazelas
5

Hier ist ein kleines Go-Programm, das die Aufgabe ausführt ( unk.go):

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    const (
        pattern     = "<unk>"
        replacement = "<raw_unk>"
    )
    var match int
    var char rune
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Split(bufio.ScanRunes)
    for scanner.Scan() {
        char = rune(scanner.Text()[0])
        if char == []rune(pattern)[match] {
            match++
            if match == len(pattern) {
                fmt.Print(replacement)
                match = 0
            }
        } else {
            if match > 0 {
                fmt.Print(string(pattern[:match]))
                match = 0
            }
            if char == rune(pattern[0]) {
                match = 1
            } else {
                fmt.Print(string(char))
            }
        }
    }
    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

Bauen Sie go build unk.goes 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.

Patrick Bucher
quelle
1
vermeidet dies das Einlesen der gesamten Datei in den Speicher?
Katze
1
Es liest die Datei zeichenweise und speichert niemals die gesamte Datei, sondern nur einzelne Zeichen.
Patrick Bucher
1
scanner.Split(bufio.ScanRunes)macht die Magie.
Patrick Bucher
Überprüfen Sie auch go doc bufio.MaxScanTokenSizedie Standardpuffergröße.
Patrick Bucher
Wie bei Ihrem CProgramm funktioniert dies nicht, wenn Sie Erdferkel durch Zebra durch eine Eingabe von Erdferkel ersetzen.
Ikarus
1

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.

Sam Rahimi
quelle
1
ja Ja es ist. "Verwenden Sie Hadoop nicht - Ihre Daten sind nicht so groß" . Dies ist ein sehr einfaches Streaming-E / A-Problem.
Sourcejedi
0

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!

DGerman
quelle
-1

Wenn wir einen Mindestbetrag von <unk>(wie von Zipfs Gesetz erwartet) haben,

awk -v RS="<unk>" -v ORS="<raw_unk>" 1
Joao
quelle
1
Nr. sedLiest eine Zeile gleichzeitig in den Speicher, unabhängig davon. Es wird nicht in der Lage sein, diese Linie zu passen.
Kusalananda
1
Ich kann keine Dokumentation finden , die nichts anderes als das GNU sagt sednicht Eingabe / Ausgabe tun Pufferung , wenn dieses Flag verwenden. Ich kann nicht sehen, dass es Teilzeilen lesen wird.
Kusalananda