gawk/Ein- und Ausgabe

Aus Foxwiki


Ein- und Ausgabe

Eingabe

Der Funktion getline kann eine Quelldatei über die Standardeingabe übergeben werden

getline [variable] [< "<Datei>"]
<Kommando> | getline [variable]
  • Im einfachsten Fall des Funktionsaufrufs ohne Argumente lädt getline einfach die nächste Eingabezeile.
  • Alle im Skript folgenden Anweisungen arbeiten folglich mit diesem neuen Datensatz.
  • In dieser Form manipuliert getline die Awk-Variablen $0, NR, NF und FNR.
  • Folgt dem Funktionsaufruf eine Variable, so landet der Inhalt der Eingabe in dieser.
  • Der Arbeitspuffer mit dem aktuellen Datensatz ($0) wird hierbei nicht verändert, jedoch werden die gelesenen Daten aus der Eingabe entfernt.

Das folgende Beispiel nutzt das Verhalten, um jeweils zwei Zeilen der Eingabe zu einer Zeile in der Ausgabe zusammenzufassen:

$ cat 2to1.awk
#!/usr/bin/awk -f
{
if ( (getline nextLine) < 1)
nextLine="";
print $0, nextLine;
}
  • getline liefert bei erfolgreich gelesener Eingabe eine »1« zurück.
  • Das Beispiel nutzt den Rückgabewert, um die Variable zurückzusetzen, falls in der Eingabe eine ungerade Anzahl Datensätze steht.
  • Eine »-1« liefert getline im Fehlerfall; eine »0« bei Erreichen des Dateiendes.

getline in Verbindung mit < "<Datei>" versucht den nächsten Datensatz aus der Datei zu lesen.

  • Dieser steht in $0 zur Verfügung, FN enthält die Anzahl der Felder dieses Dateisatzes.
  • Mit jedem Aufruf wird ein weiterer Datensatz aus der Datei geliefert.

Um von der Standardeingabe zu lesen, ist als Dateiname »-« anzugeben:

$ awk 'BEGIN {getline < "-"; print "Die Eingabe war ", $0; }'
Anmerkung
Speziell in gawk steht der Dateiname »/dev/stdin« zur Verfügung, der anstatt dem Minus verwendet werden kann.

Als weitere Quelle kann getline seine Eingaben auch aus einer Pipe beziehen.

Auf der linken Seite der Pipe muss ein Kommando stehen, das in doppelte Anführungszeichen einzuschließen ist:

$ awk 'BEGIN {"date" | getline datum; close("date"); print "Aktuelles Datum: ", datum;}'
Aktuelles Datum: Mit Dez 12 21:42:35 CET 2001

close wird im Anschluss behandelt.

Escape-Sequenzen

Escape-Sequenzen sind nicht auf die Verwendung in Ausgaben beschränkt, werden aber in diesem Zusammenhang häufig genutzt.

\a Alert (Piepton)
\b Backspace (Cursor ein Zeichen nach links)
\f Formfeed (Cursor auf nächste Zeile, eine Position weiter)
\n Zeilenumbruch
\r Carriage return (Cursor an Anfang der aktuellen Zeile)
\t Horizontaler Tabulator
\v Vertikaler Tabulator
\ddd 1-3 oktale Ziffern
\xHEX Hexadezimale Zahl
\c Das Zeichen c selbst (i.A.
  • zur Darstellung von Sonderzeichen verwendet)
$ awk 'BEGIN {print "\\f\f führt\f zu\f einem\f Treppeneffekt:)";}'
\f
führt
zu
einem
Treppeneffekt:)

Ausgabe mit print

print ([Parameter [, Parameter] ]) 

print ist für die Ausgabe zu bevorzugen, wenn Sie keine Anforderungen an die Formatierung stellen.

  • Geben Sie hierzu einfach die Liste der auszugebenden Ausdrücke, jeweils getrennt durch ein Komma, an.
  • Jedes Element wird in der Ausgabe vom Folgenden durch ein Leerzeichen getrennt werden.
  • Dem letzten Element lässt print einen Zeilenumbruch folgen (das voreingestellte Verhalten kann mittels der Variablen OFS und ORS geändert werden)
  • Sie können gar auf jegliche Elemente verzichten.
  • In dem Fall gibt print die aktuell bearbeitet Zeile aus, d.h.
  • zwischen »print« und »print $0« besteht bez.
  • des Ergebnisses kein Unterschied.
  • Die an print zu übergebenden Argumente können wahlweise in runde Klammern eingeschlossen werden.
  • Notwendig ist dies, wenn die Ausdrücke den Vergleichsoperator »>« enthalten, da dieser sonst als Ausgabeumleitung verstanden wird.
  • Es ist kein Syntaxfehler, wenn Sie auf die Kommata zwischen den auszugebenden Ausdrücken verzichten.

Awk interpretiert dies als das Zusammenhängen von Zeichenketten, sodass als ersichtlicher Unterschied zumeist das trennende Leerzeichen fehlt:

$ awk 'BEGIN { print "foo", "bar";}'
foo bar
$ awk 'BEGIN { print "foo" "bar";}'
foobar

Eine begrenzte Formatierung lässt sich für numerische Werte erzwingen, indem das in OFMT gespeicherte Ausgabeformat (Voreinstellung "%.6g") geändert wird:

$ awk 'BEGIN { print rand();}'
0.487477
$ awk 'BEGIN { OFMT="%.8f"; print rand();}'
0.48747681
$ awk 'BEGIN { OFMT="%.1f"; print rand();}'
0.5
  • Beachten Sie die enthaltene mathematisch korrekte Rundung der Werte!

Formatierte Ausgabe mit printf

Die Syntax von printf kann ihre Anlehnung an die verbreitete Sprache nicht verbergen.

printf (Formatzeichenkette[, Parameter(liste)])
  • Zwar dürfen in Awk die runden Klammern auch entfallen, aber das ist fast schon der einzige Unterschied zu C.

Die Formatzeichenkette kann jedes ASCII-Zeichen, Escape-Sequenzen oder einen der folgenden Platzhalter umfassen:

%c Ein einzelnes ASCII-Zeichen
%d Eine ganze Zahl
%i Eine ganze Zahl (wie d, aber konform zu POSIX)
%e Gleitkommazahl in Exponentendarstellung (2.e7)
%E Gleitkommazahl in Exponentendarstellung (2.E7)
%f Gleitkommazahl in Fließdarstellung
%g e oder f, je nachdem, was kürzer ist
%G E oder f, je nachdem, was kürzer ist
%o Oktale Zahl
%s Zeichenkette
%x Hexadezimale Zahl mit a-e
%X Hexadezimale Zahl mit A-E
%% % selbst
  • Die Anzahl der Elemente der Parameterliste muss mindestens die Anzahl Platzhalter in der Formatzeichenkette umfassen. Überschüssige Angaben werden schlicht ignoriert.
  • Der Typ des i-ten Parameters sollte zum i-ten Platzhalter »passen«, allerdings reagiert Awk recht großzügig und wandelt die Typen ggf. ineinander um.

Meist weicht das Resultat aber vom Gewünschten ab.

$ awk 'BEGIN { printf("%i\n", 11.100); }'
11
$ awk 'BEGIN { printf("%e\v%E\n", 11.100, 11.100); }'
1.110000e+01
1.110000E+01
$ awk 'BEGIN { printf("%f\n", 11.100); }'
11.100000
$ awk 'BEGIN { printf("%x\n", 11.100); }'
b
$ awk 'BEGIN { printf("%s\t%s\n", 11.100, "11.100"); }'
11.1 11.100
  • Eine exakte Positionierung und angepasstes Format der auszugebenden Parameter wäre mit den Platzhaltern allein nicht immer möglich, daher gestattet printf - C lässt grüßen - die Angabe von Breite und Ausrichtung eines Parameters sowie eines Modifizierers.
  • Für Gleitkommaangaben kommt noch die Genauigkeit der Nachkommastellen hinzu.

Die optionalen Angaben stehen zwischen Prozentzeichen und Formatidentifikator:

%ModifiziererBreite.GenauigkeitFormatidentifikator
  • Der wohl gebräuchlichste Modifizierer beeinflusst die horizontale Anordnung eines Parameters.

In der Voreinstellung rechtsbündig, erzwingt ein dem Prozentzeichen folgendes Minus die linksbündige Ausrichtung:

$ awk 'BEGIN { for (i=0; i<6; i++) printf("%4i\n", i**i) }'
1
1
4
27
256
3125
$ awk 'BEGIN { for (i=0; i<6; i++) printf("%-4i\n", i**i) }'
1
1
4
27
256
3125
  • Als Modifizierer stehen zur Verfügung:
- Linksbündige Ausrichtung
+ Das Vorzeichen bei nummerischen Werten wird stets angegeben
Leerzeichen Bei positiven nummerischen Werten wird ein Leerzeichen vorangestellt
0 Nummerische Werte werden von links her mit Nullen aufgefüllt
# Druckt bspw.
  • bei "%x" ein "0x" vor den Wert
$ awk 'BEGIN { printf ("%#x %04i %+4i\n", 12, 12, 12)}'
0xc 0012 +12
  • Von C's printf hat Awk ebenso die variable Angabe der Breiten- und Genauigkeitswerte übernommen.
  • Anstatt des Wertes steht im Formatierer nun ein Stern.
  • Der zu wählende Wert erscheint in der Argumentenliste vor dem jeweiligen Argument (Reihenfolge und Anzahl sollte übereinstimmen!):

Übliche Form

$ awk 'BEGIN { printf("%12.11f\n", 11/13 ) }'

Dynamische Form

$ awk 'BEGIN {width=12; precision=11; printf("%*.*f\n", width, precision, 53/17 ) }'
0.84615384615

Umleitung der Ausgabe

Die nachfolgend am Beispiel von print demonstrierten Mechanismen funktionieren ebenso mit printf.

  • Normalerweise landen die Ausgaben auf dem Terminal (Standardausgabe).

Ihr Ziel kann mittels der schon von den Shells bekannten Umleitungen ebenso eine Datei oder eine Pipe sein.

print Argument > Ausgabedatei 
Schreiben in Datei; existiert diese, wird ihr Inhalt überschrieben
print Argument >> Ausgabedatei 
Anfügen ans Ende der Datei; existiert sie nicht, wird sie erzeugt
print Argument | Kommando 
Schreiben in eine Pipe, aus der ein Kommando liest
  • Anwendungsbeispiele für den Nutzen der Umleitung von Ausgaben in einer Datei finden sich reichlich in der alltäglichen Administration, z. B. 
  • bei der Auswertung der Logdateien.
  • Gerade bei Servern häufen sich die Meldungen in der Datei /var/log/messages nur allzu schnell.
  • Per Hand regelmäßig nach verdächtigen Zeilen Ausschau zu halten, wird rasch zur Last.
  • So könnte Awk eine vorherige Selektion treffen und thematische Logdateien anlegen.
  • Das folgende Beispiel zeigt eine Anwendung, die die Nachrichten des sshd und des telnetd filtert:
  1. !/usr/bin/awk -f
# 'filter.awk' filtert Meldungen des sshd und telnetd
# Aufruf: filter.awk /var/log/messages

/.*sshd.*/ { print >> ENVIRON["HOME"]"/sshd.log"}
/.*in.telnetd.*/ { print >> ENVIRON["HOME"]"/telnet.log"}
  • Verwenden Sie in einem der Argumente von print(f) den Vergleichsoperator »>«, so müssen Sie die Argumente klammern, da der Operator ansonsten als Umleitung verstanden wird!
  • Die Ausgabe von print(f) in eine Pipe zu speisen, bietet sich an, falls das Ergebnis durch ein Kommando weiter bearbeitet werden soll.
  • Schließen Sie dazu das Kommando inklusive seiner Argumente in doppelte Anführungszeichen ein.
  • Als Beispiel werden Funde von telnet-Nachrichten sofort per Mail an den lokalen Administrator gemeldet:
#!/usr/bin/awk -f
# 'mailer.awk' meldet Nachrichten des telnetd an Root
# Aufruf: mailer.awk /var/log/messages

/.*in.telnetd.*/ { print | "mail -s 'Telnet-Kontakt' root" }
  • Derartige Kommandoaufrufe lassen sich auch in einer Variable speichern und darüber referenzieren:

...

BEFEHL="mail -s 'Telnet-Kontakt' root"
...
/.*in.telnetd.*/ { print | BEFEHL }

Spezielle Dateinamen

Analog zu den Dateideskriptoren der Shells kennt GNU-Awk (nicht POSIX!) spezielle Dateinamen, um die Umleitung in konkrete Kanäle zu realisieren.

  • Wenn Sie den letzten Befehl mit der Pipe als Muster hernehmen, sollte Ihnen zur Ausgabe über den Standardfehler-Deskriptor zumindest eine trickreiche Variante einfallen:
{ print | "cat 1>&2" }

Eleganter geht es mit der »Datei« /dev/stderr:

{ print | "/dev/stderr" }

Awk kennt folgende Dateien:

/dev/stdin Standardeingabe
/dev/stdout Standardausgabe
/dev/stderr Standardfehlerausgabe
/dev/fd/x Die mit dem Dateideskriptor x verbundene Datei
  • Ein Zugriff auf bspw. /dev/fd/5 bedingt, dass zuvor eine Datei mit dem Deskriptor verbunden wurde.

Beispiel

$ exec 5>test.log
$ awk 'BEGIN {print "Testausgabe" > "/dev/fd/5"} '

Nun raten Sie einmal, was in der Datei »test.log« drin steht? Vollständigkeitshalber sei noch erwähnt, dass Awk einige Dateien kennt, um Informationen über die Prozessumgebung auszulesen. Dies sind:

/dev/pid Prozess-ID
/dev/ppid Prozess-ID des Eltern-Prozesses
/dev/pgrpid Prozess-Gruppen-ID
/dev/user 4 Werte zum Eigentümer des Prozesses
$ awk 'BEGIN{ getline < "/dev/user";
> print "UID", $1
> print "EUID", $2
> print "GID", $3
> print "EGID", $4
> } '
UID 500
EUID 500
GID 100
EGID 100

Schließen von Dateien und Pipes

  • Wenn Sie im Laufe eines Awk-Programms mehrfach via getline Daten aus ein und derselben Datei lesen, so liefert ein Aufruf die jeweils nächste Zeile dieser.
  • Bezieht getline seine Daten aus einer Pipe und erfolgt auch hierbei der mehrfache Zugriff auf ein und denselben Befehl, so wird der Befehl nur einmalig ausgeführt und jeder getline -Aufruf bringt den nächsten Datensatz aus der Ausgabe des Befehls hervor.

Nicht immer jedoch ist dies das gewünschte Verhalten, bspw. wenn ein Befehl nur eine einzige Ausgabezeile liefert oder wir im Laufe der Berechnung an der ersten Zeile einer Datei interessiert sind:

$ awk 'BEGIN {
> for (i=0; i<2; i++) {
> "date" | getline x;
> print x;
> system("sleep 65")}
> }'
Don Dez 13 16:00:30 CET 2001
Don Dez 13 16:00:30 CET 2001
* Dass im Beispiel beide Male dieselbe Zeit ausgegeben wird, war sicherlich nicht das beabsichtigte Ergebnis.

Abhilfe schafft das Schließen der Pipe mit close:

$ awk 'BEGIN {
> for (i=0; i<2; i++) {
> "date" | getline x
> print x
> system("sleep 65")
> close("date") }
> }'
Don Dez 13 16:01:10 CET 2001
Don Dez 13 16:02:15 CET 2001
  • Verwenden Sie close im Zusammenhang mit Dateien, wenn Sie aus diesen »von vorn« lesen möchten.