
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 \n
standardmäßig eingestellt, aber das kann geändert werden. Beispiel:
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 ein3E
. 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>
.
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 vonfoobar>
durch\n<foobar>
. Beachten Sie, dass, weilRS
auf gesetzt wurde<
, alle<
aus der Eingabedatei entfernt werden (soawk
funktioniert es), also müssen wirfoobar>
(ohne<
) abgleichen und durch ersetzen\n<foobar>
.printf "%s",$0
: Drucken Sie die aktuelle „Zeile“ nach der Ersetzung.$0
ist der aktuelle Datensatz in,awk
sodass 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 awk
als auch die perl
verwendeten 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
, sed
und 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, gsar
das 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.
gsar
verwendet eine binäre Variante desBoyer-Moore-Zeichenfolgensuchalgorithmus.
Die Verwendung ist unkompliziert:
gsar -F '-s<foobar>' '-r:x0A<foobar>'
wobei -F
der „Filter“-Modus bedeutet, d. h. Lesen und stdin
Schreiben in stdout
. Es gibt auch Methoden, um mit Dateien zu arbeiten. -s
gibt die Suchzeichenfolge und -r
den 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 -b
gibt die Byte-Offsets der übereinstimmenden Suchzeichenfolge aus und gsar -l
druckt den Dateinamen und die Anzahl der Übereinstimmungen (falls vorhanden), ähnlich wie eine Kombination grep -l
mit 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 foobar
durchXXXXXX
#! /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 RT
auf das Datensatztrennzeichen, das vom Ende des aktuellen Datensatzes entfernt wurde). Beachten Sie, dass print
seine 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.