Slurp-Modus in awk?

16

Tools wie sed, awkoder perl -ndessen Eingang eines verarbeiten Datensatz zu einem Zeitpunkt, Aufzeichnungen sind Linien standardmäßig.

Einige, wie z. B. awkmit RS, GNU sedmit -zoder perlmit, -0oookönnen den Datensatztyp durch Auswahl eines anderen Datensatztrennzeichens ändern.

perl -nkann die gesamte Eingabe (jede einzelne Datei, wenn mehrere Dateien übergeben wurden) zu einem einzelnen Datensatz mit der -0777Option (oder -0gefolgt von einer Oktalzahl größer als 0377, wobei 777 die kanonische Zahl ist) machen. Das nennen sie den Slurp-Modus .

Kann etwas Ähnliches mit awk's RSoder einem anderen Mechanismus gemacht werden? Wo awkverarbeitet jeder Dateiinhalt als Ganzes im Gegensatz zu jeder Zeile jeder Datei?

Stéphane Chazelas
quelle

Antworten:

15

Sie können verschiedene Ansätze wählen, je nachdem, ob die awkBehandlung RSals einzelnes Zeichen (wie bei herkömmlichen awkImplementierungen) oder als regulärer Ausdruck (wie bei gawkoder mawk) erfolgt. Leere Dateien sind auch schwierig zu betrachten, da sie awkzum Überspringen neigen.

gawk, mawkOder andere , awkwo Implementierungen RSkann ein regulärer Ausdruck sein.

In diesen Implementierungen (zum Beispiel mawk: Beachten Sie, dass einige Betriebssysteme wie Debian eine sehr alte Version anstelle der von @ThomasDickey gepflegten modernen Version liefern ), RSist das Datensatztrennzeichen dieses Zeichen, wenn es ein einzelnes Zeichen enthält, oder wird awkin den Absatzmodus versetzt , wenn RSes leer ist. oder behandelt RSals regulären Ausdruck anders.

Die Lösung besteht darin, einen regulären Ausdruck zu verwenden, der möglicherweise nicht übereinstimmt. Manche kommen wie x^oder in den Sinn $x( xvor dem Start oder nach dem Ende). Einige (besonders mit gawk) sind jedoch teurer als andere. Bisher habe ich festgestellt, dass dies ^$das effizienteste ist. Es kann nur auf eine leere Eingabe passen, aber dann gäbe es nichts, gegen das man passen könnte.

Wir können also:

awk -v RS='^$' '{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

Eine Einschränkung ist jedoch, dass leere Dateien übersprungen werden (im Gegensatz zu perl -0777 -n). Dies kann mit GNU behoben awkwerden, indem der Code ENDFILEstattdessen in eine Anweisung geschrieben wird. Wir müssen aber auch $0in einer BEGINFILE-Anweisung zurücksetzen, da sie sonst nach der Verarbeitung einer leeren Datei nicht zurückgesetzt würde:

gawk -v RS='^$' '
   BEGINFILE{$0 = ""}
   ENDFILE{printf "%s: <%s>\n", FILENAME, $0}' file1 file2...

traditionelle awkImplementierungen, POSIXawk

In diesen Fällen RShandelt es sich nur um ein Zeichen, sie haben kein BEGINFILE/ ENDFILE, sie haben keine RTVariable, sie können das NUL-Zeichen auch im Allgemeinen nicht verarbeiten.

Sie würden denken, dass using RS='\0'dann funktionieren könnte, da sie ohnehin keine Eingaben verarbeiten können, die das NUL-Byte enthalten, aber nein, das wird RS='\0'in traditionellen Implementierungen als behandelt RS=, was der Absatzmodus ist.

Eine Lösung kann darin bestehen, ein Zeichen zu verwenden, das in der Eingabe nicht vorkommt \1. In Gebietsschemas für Mehrbytezeichen können Sie sogar Byte-Sequenzen erstellen, die sehr unwahrscheinlich sind, da sie nicht zugewiesene Zeichen oder Nicht-Zeichen wie $'\U10FFFE'in UTF-8-Gebietsschemas bilden. Nicht wirklich kinderleicht und Sie haben auch ein Problem mit leeren Dateien.

Eine andere Lösung kann darin bestehen, die gesamte Eingabe in einer Variablen zu speichern und diese am Ende in der END-Anweisung zu verarbeiten. Das heißt, Sie können jedoch immer nur eine Datei gleichzeitig verarbeiten:

awk '{content = content $0 RS}
     END{$0 = content
       printf "%s: <%s>\n", FILENAME, $0
     }' file

Das ist das Äquivalent von sed's:

sed '
  :1
  $!{
   N;b1
  }
  ...' file1

Ein weiteres Problem bei diesem Ansatz ist, dass, wenn die Datei nicht mit einem Zeilenumbruchzeichen endete (und nicht leer war), $0am Ende noch eines willkürlich hinzugefügt wird (mit würden gawkSie das umgehen, indem Sie RTanstelle von RSin das verwenden Code oben). Ein Vorteil ist, dass Sie die Anzahl der Zeilen in der Datei in NR/ notieren FNR.

Stéphane Chazelas
quelle
Was den letzten Teil betrifft ("Wenn die Datei nicht mit einem Zeilenvorschub endete (und nicht leer war), wird am Ende noch eine willkürlich in $ 0 hinzugefügt"): Bei Textdateien sollen sie eine Endung haben Neue Zeile. vi fügt beispielsweise eine hinzu und ändert somit die Datei, wenn Sie sie speichern. Ohne abschließende Zeilenumbrüche verwerfen einige Befehle die letzte "Zeile" (z. B .: wc), während andere die letzte Zeile weiterhin "sehen" ... ymmv. Ihre Lösung ist daher gültig, imo, wenn Sie Textdateien behandeln sollen (was wahrscheinlich der Fall ist, da awk gut für die Textverarbeitung ist, aber nicht so gut für Binärdateien ^^)
Olivier Dulac
1
Der Versuch, all-in zu schlürfen, kann einige Einschränkungen mit sich bringen ... traditionelles awk hatte anscheinend ein Limit von 99 Feldern in einer Zeile ... daher müssen Sie möglicherweise auch ein anderes FS verwenden, um dieses Limit zu vermeiden, aber Sie können es auch Haben Sie auch Grenzen, wie lang die Gesamtlänge einer Zeile sein kann (oder das Ganze, wenn Sie es schaffen, alles auf eine Zeile zu bekommen)?
Olivier Dulac
Endlich: Ein (alberner ...) Hack könnte darin bestehen, zuerst die gesamte Datei zu analysieren und nach einem Zeichen zu suchen, das nicht darin enthalten ist, dann tr '\n' 'thatchar' die Datei, bevor sie an awk gesendet wird, und tr 'thatchar' \n'die Ausgabe? (Sie noch append eine neue Zeile benötigen , um sicherzustellen, wie ich oben erwähnt, Ihre Eingabedatei hat ein abschließendes Newline: { tr '\n' 'missingchar' < thefile ; printf "\n" ;} | awk ..... | { tr 'missingchar' '\n' }(aber das Add a ‚\ n‘ am Ende, dass Sie bekommen müssen zu befreien ... vielleicht Hinzufügen eines Sed vor dem endgültigen Tr? Wenn dieser Tr Dateien akzeptiert, ohne Zeilenumbrüche zu beenden ...)
Olivier Dulac
@OlivierDulac, das Limit für die Anzahl der Felder würde nur erreicht, wenn wir auf NF oder ein anderes Feld zugreifen. awkspaltet nicht, wenn wir nicht. /bin/awkDavon abgesehen hatte nicht einmal Solaris 9 (basierend auf dem awk der 1970er Jahre) diese Einschränkung, daher bin ich mir nicht sicher, ob wir eine finden können, die dies tut (immer noch möglich, da SVR4s oawk ein Limit von 99 und nawk 199 hatte, also ist es wahrscheinlich wurde die Aufhebung dieses Grenzwerts von Sun hinzugefügt und kann möglicherweise nicht in anderen SVR4-basierten awks gefunden werden. Können Sie dies unter AIX testen?).
Stéphane Chazelas