Nicht zeilenorientiertes Werkzeug zum Ersetzen von Zeichenfolgen?

13

Ich habe kürzlich eine Frage gestellt, wie das Zeilenumbruchzeichen entfernt werden kann, wenn es nach einem anderen bestimmten Zeichen auftritt.

Unix-Textverarbeitungswerkzeuge sind sehr leistungsfähig, aber fast alle verarbeiten Textzeilen. Dies ist meistens dann in Ordnung, wenn die Eingabe in den verfügbaren Speicher passt.

Aber was soll ich tun, wenn ich eine Textsequenz in einer riesigen Datei ersetzen möchte, die keine Zeilenumbrüche enthält?

Zum Beispiel ersetzen <foobar>mit \n<foobar>ohne die Eingabe Zeile für Zeile zu lesen? (da es nur eine Zeile gibt und sie 2,5 GB Zeichen lang ist).

MattBianco
quelle
1
Sind Sie offen für perloder python?
Iruvar
Perl ist in Ordnung. Ich habe gerade gsar( home.online.no/~tjaberg ) gefunden, was ich versuchen werde.
MattBianco

Antworten:

12

Das erste, was mir bei solchen Problemen einfällt, ist das Ändern des Datensatztrennzeichens. In den meisten Tools ist dies \nstandardmäßig eingestellt, dies kann jedoch geändert werden. Beispielsweise:

  1. Perl

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

    Erläuterung

    • -0: Hiermit wird das Trennzeichen für den Eingabesatz auf ein Zeichen gesetzt, dessen hexadezimaler Wert angegeben wird . In diesem Fall setze ich es auf >dessen Hex-Wert 3E. Das allgemeine Format ist -0xHEX_VALUE. Dies ist nur ein Trick, um die Linie in überschaubare Teile zu unterteilen.
    • -pe: Gibt jede Eingabezeile aus, nachdem das Skript von angewendet wurde -e.
    • s/<foobar>/\n$&/: eine einfache Ersetzung. In $&diesem Fall stimmt das mit dem überein <foobar>.
  2. awk

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

    Erläuterung

    • RS="<": Setzen Sie das Trennzeichen für den Eingabesatz auf >.
    • gsub(/foobar>/,"\n<foobar>"): Ersetze alle Fälle von foobar>mit \n<foobar>. Beachten Sie, dass da RSgesetzt wurde , um <alle <von der Eingabedatei entfernt werden (das ist , wie awkfunktioniert) , so dass wir übereinstimmen müssen foobar>(ohne <) und ersetzen \n<foobar>.
    • printf "%s",$0: druckt die aktuelle "Zeile" nach der Ersetzung. $0Ist der aktuelle Rekord in awkso wird es halten, was vor dem <.

Ich habe diese mit einer einzeiligen 2,3-GB-Datei getestet, die mit den folgenden 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 perlverwendete vernachlässigbare Menge an Speicher.

terdon
quelle
Haben Sie jemals versucht, Tie::File perldoc.perl.org/Tie/File.html . Ich denke, es ist die beste Funktion Perlbeim Umgang mit großen Dateien.
Cuonglm
@Gnouc Ich habe ein bisschen damit gespielt, ja. Aber i) das OP hat in einer anderen Frage bereits eine Abneigung gegen Perl bekundet, deshalb wollte ich es einfach halten klar.
Terdon
Zustimmen. Eine kleine Notiz, die Tie::Fileseitdem ein Kernmodul ist v5.7.3.
Cuonglm
9

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

Bei den meisten Antworten auf diese Frage werden auf Datensätzen basierende Tools und verschiedene Tricks verwendet, um sie an das Problem anzupassen, z.

In vielen Fällen ist dies sehr gut und sogar lesbar. Ich mag Probleme , die leicht sein können / effizient mit überall erhältlichen Tool gelöst wie awk, tr, sedund dem Bourne - Shell.

Das Durchführen einer binären Suche und Ersetzung in einer beliebig großen Datei mit zufälligem Inhalt passt nicht sehr gut zu diesen Standard-Unix-Tools.

Einige von Ihnen denken vielleicht, dass dies ein Betrug ist, aber ich sehe nicht, wie falsch es sein kann, das richtige Werkzeug für den Job zu verwenden. In diesem Fall handelt es sich um ein C-Programm gsar, das unter der GPL v2 lizenziert ist. Daher wundert es mich sehr, dass es weder in gentoo , redhat noch in ubuntu ein Paket für dieses sehr nützliche Tool gibt .

gsarverwendet eine binäre Variante des Boyer-Moore-Suchalgorithmus .

Die Verwendung ist unkompliziert:

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

Dabei -Fbedeutet "Filter" -Modus, dh Lesen, stdinSchreiben stdout. Es gibt auch Methoden zum Bearbeiten von Dateien. -sGibt die Suchzeichenfolge und -rdie Ersetzung an. Mit der Doppelpunktnotation können beliebige Bytewerte angegeben werden.

Groß- und Kleinschreibung wird nicht berücksichtigt ( -i), reguläre Ausdrücke werden jedoch nicht unterstützt, da der Algorithmus die Länge der Suchzeichenfolge verwendet, um die Suche zu optimieren.

Das Tool kann auch nur zum Suchen verwendet werden grep. gsar -bgibt die Byte-Offsets der übereinstimmenden gsar -lSuchzeichenfolge aus und gibt den Dateinamen und die Anzahl der Übereinstimmungen aus, wenn überhaupt, ähnlich wie bei der Kombination grep -lmit wc.

Das Tool wurde von Tormod Tjaberg (Initiale) und Hans Peter Verne (Verbesserungen) geschrieben.

MattBianco
quelle
Wenn es sich um eine GPL handelt, würden Sie erwägen, sie für eine Distribution
einzupacken
1
Tatsächlich denke ich ziemlich ernsthaft darüber nach, ein Gentoo-Ebuild dafür zu machen. Vielleicht auch eine Drehzahl. Aber ich habe noch nie ein .deb-Paket erstellt, also hoffe ich, dass mich jemand schlägt (weil es einige Zeit in Anspruch nimmt).
MattBianco
Ich bezweifle, dass dies viel Trost ist, aber das Homebrew von OS X hat die Formel dafür gsar.
Crazysim
5

In dem engen Fall, in dem Ziel- und Ersatzzeichenfolgen dieselbe Länge haben, kann die Speicherzuordnung Abhilfe schaffen . Dies ist besonders nützlich, wenn der Austausch vor Ort durchgeführt werden muss. Sie ordnen eine Datei im Grunde genommen dem virtuellen Speicher eines Prozesses zu, und der Adressraum für die 64-Bit-Adressierung ist riesig. Beachten Sie, dass die Datei nicht unbedingt auf einmal im physischen Speicher abgelegt wird , sodass Dateien verarbeitet werden können, die mehrmals so groß sind wie der auf dem Computer verfügbare physische Speicher.

Hier ist ein Python - Beispiel , das ersetzt foobarmitXXXXXX

#! /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)
iruvar
quelle
4

Dafür gibt es viele Tools:

ddist das, was Sie verwenden möchten, wenn Sie eine Datei blockieren möchten - zuverlässig nur eine bestimmte Anzahl von Bytes nur eine bestimmte Anzahl von Malen lesen. Es handhabt portabel das Blockieren und Entsperren von Dateistreams:

tr -dc '[:graph:]' </dev/urandom | dd bs=32 count=1 cbs=8 conv=unblock,sync 2>/dev/null

###OUTPUT###

UI(#Q5\e BKX2?A:Z RAxGm:qv t!;/v!)N

Ich benutze tres auch oben, weil es das Konvertieren eines beliebigen ASCII-Bytes in ein beliebiges anderes (oder in diesem Fall das Löschen eines beliebigen ASCII-Bytes, das kein druckbares Zeichen ohne Leerzeichen ist) handhaben kann. Das habe ich heute Morgen bei der Beantwortung Ihrer anderen Frage verwendet, als ich Folgendes getan habe:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n' 

Es gibt viele ähnliche . Diese Liste sollte eine Teilmenge mit dem kleinsten gemeinsamen Nenner enthalten, mit der Sie möglicherweise vertraut sind.

Aber wenn ich eine Textverarbeitung mit 2,5 GB Binärdatei durchführen würde, könnte ich damit beginnen od. Es kann Ihnen ein octal dumpoder mehrere andere Formate geben. Sie können alle Arten von Optionen angeben - aber ich mache nur ein Byte pro Zeile in einem \Cmaskierten Format:

Die Daten, die Sie von erhalten od, werden regelmäßig in einem von Ihnen festgelegten Intervall abgerufen - wie ich unten zeige. Aber zuerst - hier ist eine Antwort auf Ihre Frage:

printf 'first\nnewline\ttab spacefoobar\0null' |
od -A n -t c -v -w1 |
sed 's/^ \{1,3\}//;s/\\$/&&/;/ /bd
     /\\[0nt]/!{H;$!d};{:d
    x;s/\n//g}'

Das etwas über Delimitis auf \newlines, \0nulls, \tabs und <spaces>gleichzeitig die Erhaltung der \CEscape - Sequenzen für das Trennzeichen. Beachten Sie die Hund xverwendeten Funktionen - jedes Mal , sedtrifft auf einen Begrenzer es , den Inhalt seiner Speicherpuffer auslagert. Auf diese Weise sedbleiben nur so viele Informationen erhalten, wie für eine zuverlässige Abgrenzung der Datei erforderlich sind, und es kommt nicht zu Pufferüberläufen. Solange es das tut, sedwird es seine Eingaben weiterverarbeiten und odweiter zur Verfügung stellen, bis es auf sie trifft EOF.

Die Ausgabe sieht wie folgt aus:

first
\nnewline
\ttab
 spacefoobar
\0null

Also wenn ich will foobar:

printf ... | od ... | sed ... | 
sed 's/foobar/\
&\
/g'

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Wenn Sie nun die Escape-Zeichen verwenden möchten, Cist dies ziemlich einfach - da sedbereits \\alle Backslash- printfZeichen für einzelne Eingaben durch einen doppelten Backslash maskiert wurden , hat die Ausführung von xargskeine Probleme, die Ausgabe gemäß Ihrer Spezifikation zu erstellen . Es werden jedoch xargs Shell-Anführungszeichen verwendet, sodass Sie es erneut in doppelte Anführungszeichen setzen müssen:

printf 'nl\ntab\tspace foobarfoobar\0null' |
PIPELINE |
sed 's/./\\&/g' | 
xargs printf %b | 
cat -A

###OUTPUT###

nl$
tab^Ispace $
foobar$
$
foobar$
^@null%

Das hätte genauso einfach in einer Shell-Variablen gespeichert und später auf identische Weise ausgegeben werden können. Der letzte sedfügt einen \Backslash vor jedem Zeichen in die Eingabe ein, und das ist alles.

Und so sieht alles aus, bevor es jemals in den sedGriff kommt:

printf 'nl\ntab\tspace foobarfoobar\0null' |
od -A n -t c -v -w1

   n
   l
  \n
   t
   a
   b
  \t
   s
   p
   a
   c
   e

   f
   o
   o
   b
   a
   r
   f
   o
   o
   b
   a
   r
  \0
   n
   u
   l
   l
mikeserv
quelle
2

Awk bearbeitet aufeinanderfolgende Datensätze. Es kann ein beliebiges Zeichen als Datensatztrennzeichen verwendet werden (mit Ausnahme des Null-Bytes bei vielen Implementierungen). Einige Implementierungen unterstützen beliebige reguläre Ausdrücke (die nicht mit der leeren Zeichenfolge übereinstimmen) als Datensatztrennzeichen. Dies kann jedoch unhandlich sein, da das Datensatztrennzeichen am Ende jedes Datensatzes abgeschnitten wird, bevor es gespeichert wird $0(GNU awk setzt die Variable RTauf das Datensatztrennzeichen) das wurde vom Ende des aktuellen Datensatzes entfernt). Beachten Sie, dass printdie Ausgabe mit dem Ausgabesatztrennzeichen ORSendet, das standardmäßig ein Zeilenumbruch ist und unabhängig vom Eingabesatztrennzeichen festgelegt wird RS.

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

Sie können effektiv einen anderen Charakter als Datensatztrennzeichen für andere Tools (wählen sort, sed...) durch Zeilenumbrüche mit diesem Zeichen mit Swapping tr.

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

Viele GNU-Textdienstprogramme unterstützen die Verwendung eines Null-Bytes anstelle einer Newline als Trennzeichen.

Gilles 'SO - hör auf böse zu sein'
quelle