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:
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
|