Lektion 4: Ausgabeumleitung und Pipes

Die ganzen vielen kleinen Programme nutzen ja “allein” wenig. Man muss das ganze ja verknüpfen können. Das möchte ich nun hier behandeln Ich werde anhand von einem Beispiel erläutern, wie die Konzepte anzuwenden sind. Die Theorie dahinter wird dabei auch gleich offensichtlich. Als gutes Beispiel dienen z.b. Apache (Webserver) LogDateien. Ein Standartformat dieser Dateien ist: Einzelne Zugriffe zeilenweise, Details spaltenweise:
ip – (nutzer) – datum – anfrage – antwortcode – gesendete bytes – referrer – useragent
Beispiel:
p3e9bbd6d.dip.t-dialin.net – - [10/Oct/2003:16:11:26 +0200] “GET /shellblog/index.rdf HTTP/1.1″ 304 – “-” “Mozilla/5.0 (compatible; MSIE 6.00; Windows 98)”
nun möchte ich aus dieser (derzeit circa 250000 Zeilen umfassenden) Datei folgende Information auslesen: die Referrer im Oktober 2003, für das shellblog (in dem Logfile steht alles von martin.ringehahn.de), sortiert, keine internen Referrer und keine doppelten Einträge. Mit dem > Symbol kann ich die Bildschirmausgabe (Standartausgabe) eines Befehls in eine Datei umleiten. z.b. schreibt der Befehl cat datei1 > datei2 den Inhalt der Datei 1 in Datei 2. Eventuelle Fehlermeldungen landen nicht in der Datei, da die Fehlerausgabe (StandartError) einen anderen “Kanal” verwendet und weiterhin auf dem Bildschirm erscheint. Wenn wir unsere Aufgabe mit diesem Konstrukt lösen wollen, kommt ungefähr so etwas dabei raus (die Originaldatei heisst “accesslog”, alles hinter und inkl. ‘#’ sind Kommentare von mir):

grep shellblog accesslog > datei1 # nur Zeilen, welche 'shellblog' enthalten
grep Oct/2003 datei1 > datei2 # davon nur Einträge von Oktober 2003
awk '{print $11}' datei2 > datei3 # die elfte Spalte beinhaltet den Referrer
grep http datei3 > datei4 # nur http-Adressen. damit fallen Leereinträge auch raus.
grep -v ringehahn datei4 > datei5 # keine internen Referrer, alles mit 'ringehahn' fliegt Raus
sort datei5 > datei6 # schoen alphabetisch Sortieren
uniq datei6 > datei7 # doppelte Einträge fliegen Raus.

Man kann jetzt die Dateien 1 bis 7 anschauen und sieht schön, was welches Programm gemacht hat. Dummerweise interessieren uns die Dateien 1-6 für das Endergebnis herzlich wenig. Ausserdem ist es eine höllentipperei, die keiner gern macht. Um nun die Dateien 1-6 überflüssig zu machen kommen die Pipes ins Spiel. Man stelle sich vor, dass das eine Programm seine Ausgabe direkt an das nächste weiterreicht. In den Manpages tauchen oft die Begriffe “standard input” und “standard output” auf. grep manpage: “grep searches the named input FILEs (or standard input if no files are named, or the file name – is given)”. Das Prinzip hat schon einige Jahre auf dem Buckel – die Notation wie sie heute verwendet wird, stammt aus der Zeit um 1972. Das Prinzip ist: “alles ist eine Datei”. Es hat sich bewährt. Nun hier die “kurze” und weitaus elegantere Variante (obwohl man vielleicht die “greps” in ein einziges ‘grep’ packen könnte):

grep shellblog accesslog | grep Oct/2003 | awk '{print $11}' | grep http|grep -v ringehahn | sort | uniq > datei1

oder halt ohne das > datei1 um direkt auf den Schirm zu schreiben. Ich möchte behaupten, dass so ziemlich jedes Programm in der shell mit stdin/stdout und pipes arbeiten (kann). Ich erwarte weiterhin, viel mehr sinnreiche Beispiele in den Kommentaren finden zu können :)
Nachtrag [12.10.2003]:
Sollte man mal die Funktionalität von Pipes brauchen auch wenn die Prozesse nicht direkt miteinander aufgerufen werden (können) oder sollte eines der verwendeten Programme nicht mit Stdin umgehen können dann kann man “Named Pipes” verwenden. Prinzip: man erstellt eine spezielle Datei (Named Pipe), ein Prozess schreibt in diese Datei, ein anderer liesst sie. (Kann man mit ein bisschen Feinarbeit auch verwenden um z.B. dynamische Konfigurationsdateien zu implementieren) Beispiel: Pipe erstellen:

mknod /tmp/neuepipe p

oder

mkfifo /tmp/neuepipe

um diese dann zu verwenden: cat accesslog > /tmp/neuepipe & Das Kaufmannsund dient dazu, dass dieser Prozess in den Hintergrund wandert. Hier sieht man, dass das Kommando nichts “direkt” in die Datei schreibt:

prw-r--r--  1 chrono  wheel  0 Oct 12 15:31 /tmp/neuepipe
0 Bytes in der Datei. Wenn ich jetzt diese Datei lese (z.b. mit cat /tmp/neuepipe) dann wird die Pipe “gestartet”, das eine cat “schreibt” hinein, das andere liesst aus. Wenn der erste Befehl “fertig” ist, schickt es ein “Datei ist zuende” (EOF) in die Datei und beendet sich, das andere cat beendet sich dadurch dann auch. Unter cygwin kann man jedoch derzeit keine named Pipes erstellen.

4 Kommentare zu “Lektion 4: Ausgabeumleitung und Pipes”

  1. Deifl

    [schleim :-) ]
    Du glaubst gar nicht, wie ich mich freue dich zu lesen – haben mir deine “Lektionen” doch den einen oder anderen Shell-Befehl näher gebracht.
    Ich befürchtete schon eine längere Zeit mit Abstinenz vom Shell-Blog……
    [/schleim]

    … ne ganz ehrlich: ich warte jeden Tag auf eine neue Lektion.

    P.S. Und jetzt erst mal deine Lektion 4 lesen.

  2. chrono

    wie man den stderr umleitet und aehnliches, kann man hier nachlesen:
    http://www.tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-3.html

  3. chrono

    nochwas was ich vergessen hab: mittels ‘>>’ kann man statt eine vorhandene datei zu überschreiben, inhalte anhängen

  4. knipser

    Tut gut, Text von Leuten zu lesen, die wissen wovon sie schreiben. Gut verständlich und logisch Aufgebaut.
    So nun aber zur Sache: Gutes Beispiel, mit dem access_log des Apache. Ein Hinweis: manchmal enthält der String mit dem user-agent Leerzeichen. Dann bekommt man nur den ersten Teil bis zum nächsten Leerzeichen im string von awk zurück. Abhilfe: einen anderen Separator verwenden (Parameter -F von awk). In meinem Fall das ‘”‘, welches hier noch gequotet wird, damit die shell nicht versucht es zu interpretieren.

    zcat accesslog2003-10-08.gz | awk -F” ‘{print $6}’ | sort | uniq

Einen Kommentar schreiben: