HomeSoftwareSpieleMusikForum

Lesen aus einer Pipe (bash)

Wohl jeder Bash-Programmierer [1] ist schon einmal über dieses Problem gestolpert:
a=1 b=2
echo "4711 4712" | read a b
echo "a=$a b=$b"        # Ausgabe: a=1 b=2
Allgemein gesagt, ein Kommando (im Beispiel echo) erzeugt eine Ausgabe, die man in eine oder mehrere Variablen einlesen möchte. Tut man das auf die oben gezeigten Weise, bleiben die Variablenwerte im Script unverändert. Der Grund ist, dass die Bash jeden Teil einer Pipe in einer eigenen Subshell ausführt, auch den letzten. a und b stellen lokale Variablen dieser letzten Subshell dar. Sie heißen nur zufällig so wie die Variablen der übergeordneten Shell und sind nach Beenden der Pipe nicht mehr existent. Andere Shells (z.B. ksh, zsh) lassen vernünftigerweise die letzte Pipekomponente (und nur diese) in der aktuellen Shell ablaufen, sodass das Problem dort nicht auftritt.

Eine Methode, dieses Ärgernis in der Bash zu umgehen, besteht darin, die gesamte Verarbeitung der eingelesenen Variablen in der letzten Subshell durchzuführen:

kommando | 
{
    read a b
    verarbeite $a $b
    # usw.
}
Das ist jedoch nur in verhältnismäßig einfachen Fällen möglich und sinnvoll. Allgemeiner und verbreiteter ist das folgende Verfahren:
read a b <<ENDE
$(kommando)
ENDE
oder, sofern die Shell das unterstützt:
read a b <<<$(kommando)
In beiden Fällen wird die Kommandoausgabe per Kommandoersetzung in die aktuelle Shell eingefügt und von dort als "here-document" bzw. "here-string" gelesen. Die verwendeten Variablen gehören der aktuellen Shell.

Die here-string-Methode ist die einzige, bei der man die Kommandoausgabe in "..." einschließen kann. Das ist z.B. erforderlich, wenn man eine Ausgabe zeilenweise lesen will, etwa:

while read pid tty stat time cmd
do
    echo "pid=$pid  cmd=$cmd"
done <<<$(ps h)
Da die Zeilenenden in der Kommandoausgabe durch Leerzeichen ersetzt werden, gibt diese Anweisung eine einzige lange Zeile aus:

pid=1415 cmd=0:00 -bash 1503 pts/1 S+ 0:00 vi ROX.py 1631 pts/0 S+ 0:00 vi content/de_d/shell-readfrom.html.cont 1639 pts/2 Ss 0:00 -bash 1903 pts/2 S+ 0:00 /bin/bash ./t 1904 pts/2 R+ 0:00 ps h 12697 pts/0 Ss 0:00 -bash

Im Gegensatz dazu bringt die apostrophierte Version

while read pid tty stat time cmd
do
    echo "pid=$pid  cmd=$cmd"
done <<<"$(ps h)"
das gewünschte Ergebnis:
pid=1415 cmd=0:00 -bash
pid=1503 cmd=0:00 vi ROX.py
pid=1631 cmd=0:00 vi content/de_d/shell-readfrom.html.cont
pid=1639 cmd=0:00 -bash
pid=1908 cmd=0:00 /bin/bash ./t
pid=1909 cmd=0:00 ps h
pid=12697 cmd=0:00 -bash

Statt eines here-documents lässt sich auch die folgende kleine Funktion readfrom verwenden. Das anfängliche Beispiel würde damit so aussehen:

text=$(kommando)
readfrom "$text" a b
oder kurz
readfrom "$(kommando)" a b
Mit der Option -tc wird das Trennzeichen c anstelle von $IFS verwandt, z.B.
readfrom -t: "$(grep "Meier" /etc/passwd)" user x uid gid


[1] Anwesende natürlich ausgenommen

up
Created 2011-08-11 by mopcoge