nicht zeilenorientiertes Tool zum String-Ersatz?

nicht zeilenorientiertes Tool zum String-Ersatz?

Ich habe vor kurzem gefragteine FrageInformationen zum Entfernen des Zeilenumbruchzeichens, wenn es nach einem anderen bestimmten Zeichen auftritt.

Unix-Textverarbeitungstools sind sehr leistungsfähig, aber fast alle von ihnen verarbeiten Textzeilen, was in den meisten Fällen kein Problem darstellt, wenn die Eingabe in den verfügbaren Speicher passt.

Was aber soll ich tun, wenn ich eine Textsequenz in einer großen Datei ersetzen möchte, die keine Zeilenumbrüche enthält?

Beispielsweise <foobar>durch ersetzen \n<foobar>, ohne die Eingabe zeilenweise zu lesen? (da nur eine Zeile vorhanden ist und diese 2,5 GB Zeichen lang ist).

Antwort1

Das Erste, was mir bei dieser Art von Problem einfällt, ist, den Datensatztrenner zu ändern. In den meisten Tools ist dies \nstandardmäßig eingestellt, aber das kann geändert werden. Beispiel:

  1. Perl

    perl -0x3E -pe 's/<foobar>/\n$&/' file
    

    Erläuterung

    • -0: Dadurch wird der Eingabedatensatztrenner auf ein Zeichen mit der entsprechendenHexadezimalwert. In diesem Fall stelle ich es auf >dessen Hexadezimalwert ein 3E. Das allgemeine Format ist -0xHEX_VALUE. Dies ist nur ein Trick, um die Zeile in überschaubare Abschnitte aufzuteilen.
    • -pe: Drucken Sie jede Eingabezeile, nachdem Sie das von angegebene Skript angewendet haben -e.
    • s/<foobar>/\n$&/: eine einfache Ersetzung. Das $&ist, was auch immer übereinstimmt, in diesem Fall <foobar>.
  2. awk

    awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
    

    Erläuterung

    • RS="<": Setzen Sie den Eingabedatensatztrenner auf >.
    • gsub(/foobar>/,"\n<foobar>"): Ersetzen Sie alle Fälle von foobar>durch \n<foobar>. Beachten Sie, dass, weil RSauf gesetzt wurde <, alle <aus der Eingabedatei entfernt werden (so awkfunktioniert es), also müssen wir foobar>(ohne <) abgleichen und durch ersetzen \n<foobar>.
    • printf "%s",$0: Drucken Sie die aktuelle „Zeile“ nach der Ersetzung. $0ist der aktuelle Datensatz in, awksodass alles enthalten bleibt, was vor dem stand <.

Ich habe dies an einer 2,3 GB großen einzeiligen Datei getestet, die mit diesen Befehlen erstellt wurde:

for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file

Sowohl die awkals auch die perlverwendeten vernachlässigbare Mengen an Speicher.

Antwort2

gsar (allgemeines Suchen und Ersetzen)ist genau für diesen Zweck ein sehr nützliches Werkzeug.

Die meisten Antworten auf diese Frage verwenden datensatzbasierte Tools und verschiedene Tricks, um sie an das Problem anzupassen. So wird beispielsweise das Standard-Datensatztrennzeichen durch ein Zeichen ersetzt, das in der Eingabe häufig genug vorkommt, damit die einzelnen Datensätze nicht zu groß für die Verarbeitung werden.

In vielen Fällen ist das sehr gut und sogar lesbar. Ich mag Probleme, die mit überall verfügbaren Tools wie awk, tr, sedund der Bourne-Shell einfach/effizient gelöst werden können.

Das Durchführen einer binären Suche und Ersetzung in einer beliebig großen Datei mit zufälligem Inhalt ist für diese Standard-Unix-Tools nicht besonders gut geeignet.

Manche von Ihnen denken vielleicht, das sei Betrug, aber ich sehe nicht, wie es falsch sein kann, das richtige Werkzeug für die Aufgabe zu verwenden. In diesem Fall handelt es sich um ein C-Programm namens, gsardas lizenziert ist unterGPL v2, daher überrascht es mich ziemlich, dass es kein Paket für dieses sehr nützliche Tool gibt, weder inGentoo,roter Hut, nochUbuntu.

gsarverwendet eine binäre Variante desBoyer-Moore-Zeichenfolgensuchalgorithmus.

Die Verwendung ist unkompliziert:

gsar -F '-s<foobar>' '-r:x0A<foobar>'

wobei -Fder „Filter“-Modus bedeutet, d. h. Lesen und stdinSchreiben in stdout. Es gibt auch Methoden, um mit Dateien zu arbeiten. -sgibt die Suchzeichenfolge und -rden Ersatz an. Die Doppelpunktnotation kann verwendet werden, um beliebige Bytewerte anzugeben.

Der Case-Insensitive-Modus wird unterstützt ( -i), reguläre Ausdrücke werden jedoch nicht unterstützt, da der Algorithmus die Länge des Suchstrings zur Optimierung der Suche verwendet.

Das Tool kann auch nur zum Suchen verwendet werden, ähnlich wie grep. gsar -bgibt die Byte-Offsets der übereinstimmenden Suchzeichenfolge aus und gsar -ldruckt den Dateinamen und die Anzahl der Übereinstimmungen (falls vorhanden), ähnlich wie eine Kombination grep -lmit wc.

Das Tool wurde geschrieben vonTormod Tjaberg(initial) undHans Peter Verne(Verbesserungen).

Antwort3

Im engen Fall, dass Ziel- und Ersatzzeichenfolge gleich lang sind,Speicherzuordnungkann Abhilfe schaffen. Dies ist insbesondere dann nützlich, wenn der Ersatz direkt vor Ort durchgeführt werden muss. Sie ordnen im Grunde eine Datei dem virtuellen Speicher eines Prozesses zu, und der Adressraum für 64-Bit-Adressierung ist riesig.Beachten Sie, dass die Datei nicht unbedingt auf einmal in den physischen Speicher abgebildet wird, sodass Dateien verarbeitet werden können, die ein Vielfaches der Größe des auf dem Computer verfügbaren physischen Speichers betragen.

Hier ist ein Python-Beispiel, das ersetzt foobardurchXXXXXX

#! /usr/bin/python
import mmap
import contextlib   
with open('test.file', 'r+') as f:
 with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
   pos = 0
   pos = m.find('foobar', pos)
   while pos > 0:
    m[pos: pos+len('XXXXXX')] = 'XXXXXX'
    pos = m.find('foobar', pos)

Antwort4

Awk arbeitet mit aufeinanderfolgenden Datensätzen. Es kann jedes beliebige Zeichen als Datensatztrennzeichen verwenden (außer dem Nullbyte bei vielen Implementierungen). Einige Implementierungen unterstützen beliebige reguläre Ausdrücke (die nicht mit der leeren Zeichenfolge übereinstimmen) als Datensatztrennzeichen, aber das kann unhandlich sein, da das Datensatztrennzeichen vom Ende jedes Datensatzes abgeschnitten wird, bevor es darin verstaut wird $0(GNU awk setzt die Variable RTauf das Datensatztrennzeichen, das vom Ende des aktuellen Datensatzes entfernt wurde). Beachten Sie, dass printseine Ausgabe mit dem Ausgabe-Datensatztrennzeichen beendet wird ORS, das standardmäßig eine neue Zeile ist und unabhängig vom Eingabe-Datensatztrennzeichen gesetzt wird RS.

awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'

Sie können effektiv ein anderes Zeichen als Datensatztrennzeichen für andere Tools ( sort, sed, …) auswählen, indem Sie Zeilenumbrüche mit diesem Zeichen durch ersetzen tr.

tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'

Viele GNU-Textdienstprogramme unterstützen die Verwendung eines Nullbytes anstelle eines Zeilenumbruchs als Trennzeichen.

verwandte Informationen