Kernel (de) Kernel (it) Books Shell Sed/awk RAM/swap P35

Eine kurze Einführung zu Shell-Ausgabeumleitung

Autor und Copyright: Dr. Thomas Hertweck
Version: 0.1; Stand: 02. August 2004

Zu diesem Dokument

Die neuste Version dieses Dokuments kann stets unter https://www.thomashertweck.de/redir.html gefunden werden.

Diese kleine Einführung zur Ausgabeumleitung in der bash entstand (in etwas anderer Form) für eine Vorlesung am Geophysikalischen Institut der Universität Karlsruhe. Dieser Text erhebt natürlich keinen Anspruch auf Richtigkeit und Vollständigkeit und es wird keine Gewähr für alle Angaben oder daraus entstehender Konsequenzen übernommen. Ich hoffe aber, dieses Dokument wird der ein oder anderen Person hilfreich sein.

Einleitung

Immer wieder taucht die Frage auf, wie die Standardausgabe (stdout) bzw. die Standardfehlerausgabe (stderr) eines Programms umgeleitet werden kann (z.B. in eine Datei) oder wie die entsprechende Ausgabe an ein anderes Programm weitergeleitet werden kann (Pipe). Im Manual zur Shell "bash" (siehe man bash) findet man dieses Thema im Kapitel "Redirection" - allerdings ist das Thema dort kurz und bündig dargestellt und ein paar Feinheiten sind auf den ersten Blick nicht unbedingt ersichtlich. Hier sollen nun ein paar typische Fälle der Ausgabeumlenkung, die immer wieder auftauchen, näher erläutert werden. Mit Umlenkung der Standardeingabe werden wir uns nicht befassen.

Um die Erklärungen besser nachvollziehen zu können, benutzen wir ein kleines Shell-Skript, das wie folgt aussieht:

#!/bin/bash
echo "Dies ist eine Ausgabe auf stdout." >&1
echo "Dies ist eine Ausgabe auf stderr." >&2

Beim Aufruf dieses kleinen Skriptes, das wir fortan mit redir.sh betiteln wollen, werden zwei Ausgaben produziert, eine auf die Standardausgabe, d.h. stdout, und eine andere auf die Standardfehlerausgabe, d.h. stderr:

$> redir.sh
Dies ist eine Ausgabe auf stdout.
Dies ist eine Ausgabe auf stderr.

(Anmerkung: $> stellt den Shell-Prompt dar.) Die Syntax aus oben angegebenem Skript sollte gleich klarer werden - zum momentanen Zeitpunkt nehmen wir das mal als gegeben hin. Ohne Ausgabeumlenkung werden standardmäßig sowohl stdout als auch stderr auf dem Bildschirm (Konsole) ausgegeben, was man leicht beim Aufruf des Skriptes verfolgen kann (siehe oben).

Umleitung von stdout

Möchte man nun die Standardausgabe (stdout) umleiten (z.B. in eine Datei, in unserem folgenden Beispiel nehmen wir statt einer Datei aber der Einfachheit halber das Device /dev/null, also den Mülleimer des Systems), so kann man das mit dem Operator 1> bzw. > tun - beide Operatoren sind identisch, letztere Schreibweise ist etwas kürzer. Die Zahl 1 steht dabei für einen sog. Dateideskriptor (file descriptor), der auch im Folgenden immer wieder auftaucht. Vordefiniert sind die folgenden Dateideskriptoren:

0 = Standardeingabe (/dev/stdin)
1 = Standardausgabe (/dev/stdout)
2 = Standardfehlerausgabe (/dev/stderr)

Man beachte, dass hier zwischen dem Dateideskriptor und dem Größer-Zeichen kein Leerzeichen stehen darf, sonst produziert der Befehl evtl. nicht das, was man erwarten würde. Referenzieren kann man die Dateideskriptoren mit &0 bzw. &1 bzw. &2. Das wird sicherlich im Laufe der folgenden Beispiele klar. In unserem Beispiel mit redir.sh passiert nun folgendes:

$> redir.sh 1> /dev/null
Dies ist eine Ausgabe auf stderr.

Die Standardausgabe wird also im Mülleimer /dev/null entsorgt und taucht daher nicht mehr auf dem Bildschirm auf, übrig bleibt nur die Standardfehlerausgabe. Identisch dazu wäre die Eingabe von

$> redir.sh > /dev/null

als etwas kürzere Schreibweise - sie produziert die gleiche Ausgabe wie oben.

Umleitung von stderr

Möchte man die Standardfehlerausgabe (stderr) umleiten, so kann man in logischer Fortsetzung des bisherigen Beispiels den Operator 2> benutzen. Die Angabe des Dateideskriptors kann hier natürlich nicht weggelassen werden (es gibt also keine verkürzte Schreibweise), da wir ja sonst wieder den Operator für die Umleitung von stdout reproduzieren würden. Unser Beispiel liefert hier

$> redir.sh 2> /dev/null
Dies ist eine Ausgabe auf stdout.

was jedem einleuchten sollte. Die Standardfehlerausgabe wird im Mülleimer /dev/null entsorgt und übrig bleibt die Standardausgabe auf dem Bildschirm (Konsole).

Umleitung von stdout und stderr

Möchte man sowohl stdout als auch stderr gleichzeitig umlenken, so kann man hierfür den Operator &> benutzen. Auch hier wiederum achte man darauf, keine Leerzeichen zwischen dem &- und dem >-Zeichen einzufügen!

$> redir.sh &> /dev/null

Da nun sowohl stdout als auch stderr gleichzeitig im Mülleimer /dev/null entsorgt werden, erscheint keine Ausgabe mehr auf dem Bildschirm (Konsole). Auch dieses Beispiel sollte einleuchten. Es kann übrigens auch durch die folgende Eingabe realisiert werden (die erste oben angegebene Variante sollte aber vorgezogen werden):

$> redir.sh >& /dev/null

Die Reihenfolge von Ausgabeumlenkungen

Kommen wir nun aber zu interessanteren Fällen. Dazu betrachten wir uns zunächst die beiden folgenden Versuche:

Wie man sofort anhand der beiden Versuche sieht, hängt die Wirkung von Ausgabeumleitungen von der Reihenfolge ab, wie sie angegeben werden. Werden beim 1. Versuch sowohl stdout als auch stderr in den Mülleimer umgeleitet (deswegen erscheint keine Ausgabe mehr auf dem Bildschirm), so wird beim 2. Versuch lediglich stdout in den Mülleimer umgelenkt, stderr erscheint weiterhin auf dem Bildschirm (Konsole). Insbesondere mag nun dieser 2. Versuch auf den ersten Blick völlig unlogisch erscheinen und nicht das produzieren, was manche erwartet hätten. Die Erklärung ist aber relativ einfach:

Um zu verstehen, was passiert, muss man wissen, dass die Wirkung der Ausgabeumlenkung von links nach rechts (also in der natürlichen Reihenfolge wie man auch liest) auszuwerten ist. Beim 1. Versuch wird zunächst die Standardausgabe (stdout) über den Operator > in den Mülleimer umgeleitet. Im Anschluss daran wird stderr auf stdout umgeleitet (dupliziert), oder genauer: stderr wird auf den Ort umgeleitet, auf den stdout zum Zeitpunkt der Umlenkung zeigt. Das ist hier /dev/null. Daher landet auch die Standardfehlerausgabe im Mülleimer und auf dem Bildschirm bleibt keine Ausgabe übrig. Es erscheint schlicht wieder der Shell-Prompt.

Beim 2. Versuch wird zunächst stderr auf stdout umgeleitet (dupliziert), oder anders ausgedrückt: stderr wird auf den Ort umgeleitet, auf den stdout zum Zeitpunkt der Umleitung zeigt - das ist der Bildschirm (Konsole). Man muss nun realisieren, dass die Dateideskriptoren zu stdout und stderr trotz der Umleitung weiterhin als getrennt zu betrachten sind (weswegen man manchmal eben auch eher von duplizieren redet). Im Anschluss an die erste Umleitung wird nun die Standardausgabe über den Operator > in den Mülleimer gelenkt. Der Dateideskriptor 2, also stderr, zeigt aber weiterhin auf die Bildschirmausgabe (dorthin wurde er ja vorher dupliziert), sodass der Text zu stderr wie oben im Beispiel zu sehen auf der Konsole erscheint. Wichtig zu realisieren ist also, dass unter Beachtung der Reihenfolge der Umleitungen zunächst stderr auf stdout und dann stdout (und nur stdout) nach /dev/null geleitet wird.

Ausgabeweiterleitung und Pipes

Kommen wir nun zu sog. Pipes, die durch den Operator | realisiert werden: eine Pipe verknüpft die Standardausgabe eines Programms A mit der Standardeingabe eines Programms B. Man beachte, dass hier nur von Standardausgabe, nicht von Standardfehlerausgabe, die Rede ist. Durch

$> progA | progB

wird also lediglich stdout von progA an progB geleitet - eine evtl. stattfindende Fehlerausgabe auf stderr von progA würde auf dem Bildschirm (Konsole) erscheinen. Soll sowohl stdout als auch stderr von progA an progB geleitet werden, so muss man

$> progA 2>&1 | progB

benutzen, d.h. die Standardfehlerausgabe wird vor der Pipe auf der Standardausgabe dupliziert. Das Problem hat man z.B., wenn man die komplette Ausgabe eines make-Befehls (also die normal durchgefuehrten Aktionen und evtl. auftauchende Fehler) protokollieren möchte. Das geht z.B. sehr elegant mit dem Konstrukt

$> make target 2>&1 | tee make.log

Für Details, siehe u.a. man tee. Möchte man nur stderr von progA nach progB leiten, so ist (wie oben ausführlich erläutert) die folgende Sequenz zu nutzen:

$> progA 2>&1 > /dev/null | progB

Kurze Erklärung: Hier wird zunächst stderr auf stdout umgeleitet (d.h. stderr zeigt nun dorthin, wo auch stdout hin zeigt), im Anschluss daran wird stdout nach /dev/null umgeleitet. Übrig bleibt also nur das ursprüngliche stderr, was nun (da vorher korrekt dupliziert) über die Pipe an progB weiter geleitet wird. Alle Klarheiten beseitigt?? Einen Operator 2|, wie es manchen nun evtl. sinnvoll erscheinen könnte, um die Standardfehlerausgabe durch eine Pipe zu schicken, gibt es übrigens nicht.

Hat man das Prinzip einmal verstanden, so kann man gängige Konstrukte zur Ausgabeumlenkung systematisch analysieren. Es sollte nun zum Beispiel nach obigen Erläuterungen klar sein, warum ein Konstrukt wie

$> progA 1>&2 | progB

nie funktionieren kann! Beliebige weitere Beispiele (sinnvolle und weniger sinnvolle) lassen sich natürlich leicht angeben. Es sollte nun aber hoffentlich die Manual-Seite man bash und das dortige Kapitel "Redirection" etwas einleuchtender erscheinen. Verbesserungsvorschläge zu diesem Text, Korrekturhinweise, Lob und Kritik sind stets erwünscht und willkommen.