Exit Status
Jedes Kommando liefert einen Exit Status zuruck. Der Exit Status
ist ein Integerwert zwischen 0 und 255. 0 bezeichnet den Erfolg des
Kommandos, alle anderen Werte bezeichnen verschiedene Ausmaße
des Scheiterns. Der Exit Status des letzten Kommandos steht in der
Variable $?.
$ grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
$ echo $?
0
$ grep Administrator /etc/passwd
$ echo $?
1
$ true
$ echo $?
0
$ false
$ echo $?
1
Die Programme true beziehungsweise false dienen einzig und allein
dazu Exit Status 0 beziehungsweise 1 zu liefern. Das scheint auf den
ersten Blick sinnlos, aber das tut auch /dev/null.
Um den Exit Status eines Shell Scripts zu beeinflussen, gibt es den
built-in Befehl exit. Dieser beendet ein Script sofort und setzt einen
anzugebenden Wert als Exit Status des Scripts. Das Script namens
parameterexists liefert Exit Status 0, wenn mindestens ein Parameter
übergeben wurde. Andernfalls liefert es Exit Status 1.
#!/bin/bash
if [ $1 ]; then
exit 0
else
exit 1
fi
Man sollte dabei umbedingt die Konvention beachten, daß 0 fur einen
Erfolg steht, während andere Zahlen als Mißerfolg interpretiert wer-
den.
|
Exit Status |
|
Jedes Programm liefert einen Exit Status |
|
zurück. |
|
0 steht fur eine erfolgreiche Beendigung des |
|
Programms |
|
1-255 bezeichnet verschiedene Ausmaße des |
|
Scheiterns. |
|
Der Exit Status eines Shell Scripts kann über |
|
den bash built-in Befehl exit gesteuert werden. |
|
Der Exit Status des letzten ausgeführten |
|
Kommandos steht in der Variable $?. |
Flow Control
Unter Flow Control versteht man Kontrollstrukturen wie bedingtes
Ausfuhren von Kommandos und Schleifen. Im Wesentlichen sind dies
if, case, for und while. Wir werden diese im Weiteren nachein-
ander abhandeln, wobei wir nebenbei allerlei Nützliches mitnehmen
werden. Zuerst wenden wir uns dem if zu, von dem wir schon ein
Beispiel sahen. Im folgenden Syntaxüberblick wird bei zutreffen von
Bedingung1 die Anweisung1 ausgeführt. Trifft Bedingung1 nicht, Be-
dingung2 aber schon zu, so wird Anweisung2 ausgefuhrt. Trifft keine
der beiden Bedingungen zu, so wird Anweisung3 ausgeführt.
|
Flow Control, if |
|
Die if-Anweisung erlaubt eine bedingte |
|
Ausfuhrung von Kommandos und ist dem if in |
|
Programmiersprachen sehr ähnlich. Es verlangt |
|
nach folgender Syntax: |
|
if Bedingung1 |
|
then |
|
Anweisung1 |
|
elif Bedingung2 |
|
then |
|
Anweisung2 |
|
else |
|
Anweisung3 |
|
fi |
|
wobei sowohl der elif, als auch der else-Block |
|
optional sind. Die Bedingung ist ein |
|
Kommando, dass 0(true) oder ungleich 0 (false) als |
|
Exit Status zurückgibt. |
Bedingungen
Um ein if formulieren zu konnen, müssen wir wissen, was eine Be-
dingung ist. Eine Bedingung ist nichts anderes als ein Kommando. Ist
dessen Exit Status 0, so gilt die Bedingung als erfüllt. Andernfalls gilt
sie als nicht erfüllt. Das erklart auch, warum man den Konventionen
des Exit Status in eigenen Scripts folgen sollte.
Ein in Bezug auf Bedingungen besonders wertvoller Befehl ist der
built-in Befehl test. Will man zum Beispiel feststellen, ob der Inhalt
der Variable TEST gleich “rose” ist, so kann man das so überprufen:
$ TEST=rose
$ test $TEST = rose
$ echo $?
0
$ TEST=eros
$ test $TEST = rose
$ echo $?
1
Als Synonym für test können auch eckige Klammern [ ] verwendet
werden, wobei darauf zu achten ist, daß nach der öffnenden und vor
der schließenden Klammer Leerzeichen stehen.
$ TEST=rose
$ [ $TEST = rose ]
$ echo $?
0
Bedingungen (Strings, Dateien)
Das ist eine unvollständige Liste von Tests mit
Strings (Zeichenketten) und Dateien. In
folgender Liste bezeichnen S1 beziehungsweise
S2 Strings und D1 beziehungsweise D2
Dateinamen.
Test
Wahr wenn
[ S1 = S2 ]
Strings ident
[ S1 != S2 ]
Strings nicht ident
. . .
[ -e D1 ]
Datei D1 existiert
[ -d D1 ]
D1 ist ein Verzeichnis
[ -x D1 ]
D1 ist ausführbar
[ D1 -nt D2 ] D1 neuer als D2
. . .
|
Bedingungen (Integers) |
|
Die folgenden Tests gehen davon aus, dass in |
|
den Variablen A und B ganze Zahlen stehen. Ist |
|
dies nicht so, so gibt es eine Fehlermeldung. |
|
Test |
|
Wahr wenn |
|
[ $A -lt $B ] $A kleiner als $B |
|
[ $A -gt $B ] $A größer als $B |
|
[ $A -le $B ] $A kleiner gleich $B |
|
[ $A -ge $B ] $A größer gleich $B |
|
[ $A -eq $B ] $A gleich $B |
|
[ $A -ne $B ] $A ungleich $B |
Arithmetik
Neben der Möglichkeit ganze Zahlen gegeneinander zu testen, kann
man in der Shell mit ganzen Zahlen auch Rechnen. Ausdrücke, die mit
$[ ] oder $(()) umschlossen sind werden als ganzzahlige Rechenope-
rationen ausgelegt und auszuwerten versucht.
$ echo "1+1=$[1+1]"
1+1=2
$ echo "3*3=$((3*3))"
3*3=9
$ echo "11/4=$[11/4] mit Rest $[11%4]"
11/4=2 mit Rest 3
In der Programmierung ist es häufig gefragt, den Inhalt einer Variable
zu inkrementieren, sprich um 1 zu erhöhen. Das sieht in C definitiv
schöner aus, als in der bash:
$ N=1
$ echo $[$N+1]
2
Um in der Shell oder innerhalb von Scripts Gleitkommarechnungen
oder allgemein kompliziertere Rechnungen durchzuführen, sei dem Le-
ser bc und dc ans Herz gelegt.
|
Arithmetik |
|
Integerarithmetik wird innerhalb eckiger oder |
|
doppelter runder Klammern ausgewertet. |
|
$ echo $[1+1] |
|
$ echo $((1-1)) |
|
Eine unvollstandige Liste der existierenden |
|
Operatoren: |
|
Operator Bedeutung |
|
+ |
|
Plus |
|
- |
|
Minus |
|
* |
|
Multiplikation |
|
/ |
|
Division |
|
% |
|
Modulo |
|
** |
|
Exponent |
|
Das Inkrementieren einer Variable funktioniert |
|
folglich so: |
|
$ N=1 |
|
$ N=$[$N+1] |
|
$ echo $N |
Bedingungen Verknüpfen
Neben dem if gibt es noch eine einfachere, eingeschränkte Möglichkeit
des bedingten Ausführens von Kommandos. So wird im Folgenden der
Befehl ls nur ausgeführt, wenn die Programmdatei /bin/ls existiert
und ausführbar ist:
$ [ -x /bin/ls ] && ls
helloworld parameterexists parameters
Der Exit Status solch einer Kette von Kommandos ist der Exit Status
des letzten ausgeführten Kommandos. Ein weiteres Beispiel legt eine
Datei foo nur an, wenn diese noch nicht existiert:
$ touch foo
$ ls
foo
$ [ -e foo ] || touch foo
$ echo $?
0
$ [ ! -e foo ] && touch foo
$ echo $?
1
|
Bedingtes Ausführen |
|
In folgender Zeile wird Kommando2 ausgeführt, |
|
wenn Kommando1 Exit Status 0 hat. |
|
$ Kommando1 && Kommando2 |
|
Während in folgender Zeile Kommando2 |
|
ausgeführt wird, wenn Kommando1 Exit Status |
|
= 0 hat. |
|
$ Kommando1 || Kommando2 |
|
Der Exit Status dieser Zeilen ist der Exit |
|
Status des letzten ausgefuhrten Kommandos. |
|
Ein ! vor einem Kommando verneint den Exit |
|
Status. |
|
Operator Bedeutung |
|
&& -- Und |
|
|| -- Oder |
|
! -- Nicht |
|
|
|
|
|
|
|
Dies kann auch dazu benutzt werden |
|
verknupfte Bedingungen in einer if-Anweisung |
|
zu erstellen. |
Flow Control, case
Eine spezielle und für manche Anwendungen
sehr angenehme Variante der if-Anweisung ist
die case-Anweisung, die nach folgender Syntax
verlangt.
case Ausdruck in
Pattern1)
Anweisungen ;;
Pattern2)
Anweisungen ;;
...
esac
Das bietet sich zum Beispiel dazu an, um
Switches zu realisieren. Wie bei init-scripts
üblich, erlaubt folgendes Script eine “start” und
“stop” Option:
case $1 in
start) ...;;
stop) ...;;
*) Usage: ...;;
esac
Die case-Anweisung wird oft in init Scripts verwendet. Diese Scripts
dienen zum starten und stoppen von Systemdiensten. Üblicherweise
liegt für jeden Systemdienst im Verzeichnis /etc/init.d/ (manchmal
auch /etc/rc.d/init.d oder /etc/rc.d/) ein Script, dass diese die
Parameter start, stop oder restart versteht.
$ cat /etc/init.d/inetd
#!/bin/sh
#
# start/stop inetd super server.
[...]
case "$1" in
start)
echo -n "Starting internet superserver:"
[...]
;;
stop)
echo -n "Stopping internet superserver:"
[...]
;;
restart)
echo -n "Restarting internet superserver:"
[...]
;;
*)
echo "Usage: /etc/init.d/inetd {start|stop|restart}"
exit 1
;;
Gibt man einen Parameter an, der nicht angeführt ist, so wird der
Punkt unter *) ausgefuhrt. Dieser klärt über die richtige Benutzung
des Scripts auf und beendet das Script mit Exit Status 1.
Die for-Schleife unterscheidet sich stark von for-Schleifen anderer
Programmiersprachen. In ihr durchläuft ein Variable alle Werte einer
Liste. Die Eintrage dieser Liste sind durch Leerzeichen, Tabulatoren
oder Zeilenumbruch getrennt. Alle Frauen sind herzlichst dazu einge-
laden das Script polygam nach ihren Bedürfnissen zu modifizieren.
#!/bin/bash
LISTE="Liese Ingrid Stefanie Frauke"
for FRAU in $LISTE; do
echo "$FRAU ist meine Frau."
done
Eine Möglichkeit einer Konstruktion eines in anderen Sprachenübli-
chen for ist die Folgende:
|
Flow Control, for |
|
Die for-Schleife der bash ist in ihrer Syntax |
|
substanziell verschieden zu vielen |
|
Programmiersprachen. |
|
for Name in Liste |
|
do |
|
Anweisungen |
|
done |
|
Der Inhalt der Variable Name durchläuft dabei |
|
alle Elemente der Liste Liste. So spielt der |
|
folgende Code alle mp3-Dateien im aktuellen |
|
Verzeichnis ab. |
|
for LIED in *.mp3 |
|
do |
|
mpg123 $LIED |
|
done |
|
mpg123 ist dabei der mp3-Player. *.mp3 wird |
|
von der Bash zu einer Liste aller Dateien mit |
|
der Endung .mp3 expandiert. |
Command Substitution
Oft will man an einer bestimmten Stelle in einem Script die Ausgabe
eines Kommandos platzieren. Das nennt man Command Substitution
und läßt sich auf zwei Arten realisieren. Das Kommando wird entwe-
der mit Backticks ‘‘ oder mit $() umschlossen. Das Script summerton
ist der Telefonzeitansage nachempfunden:
#!/bin/bash
# Zeitausgabe ala Zeitansage per Telefon
# Warten bis zu den naechsten vollen 10 Sekunden
while [ $[‘date +%S‘%10] -ne 0 ]; do
sleep 1
done
# Alle 10 Sekunden Zeitansage
while true; do
echo "Es wird mit dem Summerton ‘date +%k‘ Uhr, \
‘date +%M‘ Minuten und ‘date +%S‘ Sekunden."
sleep 10
done
Die Frage ob man ‘‘ oder $() verwendet ist nicht ganz irrelevant. Die
Verwendung von $() hat den Vorteil, daß man diese Konstruktion in-
einander Verschachteln kann. Der Nachteil ist, daß diese Konstruktion
in der bash, nicht jedoch in der sh existiert. Versucht man ein Script,
dass $() verwendet auf einem System ohne einer bash zu verwenden,
so wird dies scheitern.
Das übliche For I
Um das in anderen Programmiersprachen übliche Verhalten einer for-
Schleife zu erhalten kann folgender Code in usualfor1 dienen:
#!/bin/bash
# Immitiert ein for wie in C, wobei dies einem
# for(int i=$START, i<=$STOP, i=i+$STEP) entspricht
START=1; STOP=10; STEP=3
for N in ‘seq $START $STEP $STOP‘
do
echo $N
done
$N durchläuft alle Werte von $START bis $STOP in Abständen von
$STEP.
Zur Command Substitution werden hier Backticks (‘‘) verwendet,
was die Portabilitat des Codes erhoht. In den meisten Fallen kann auf
eine einfachere Syntax von seq zurückgegriffen werden. Siehe dazu
$ man seq
|
Flow Control, while, until |
|
Die while- bzw. until-Schleife entspricht ihren |
|
Entsprechungen in anderen |
|
Programmiersprachen. Ein Anweisungsblock |
|
wird, solange eine Bedingung wahr bzw. nicht |
|
wahr ist, ausgeführt. |
|
while Bedingung |
|
do |
|
Anweisungen |
|
done |
|
Als Beispiel ein Codesegment, das alle 5 |
|
Sekunden schaut, ob eine Modemverbindung |
|
existiert und falls diese zusammengebrochen ist |
|
eine Funktion namens reconnent() ausführt. |
|
while ifconfig | grep ppp0 |
|
do |
|
sleep 5 |
|
done |
|
reconnect() |
Das übliche For II
Eine weitere Methode das in anderen Programmiersprachen übliche
for zu erhalten ist die folgende Umschreibung in usualfor2, die naturlich
nicht Shell spezifisch ist:
#!/bin/bash
# umschreibt ein for wie in C
# durch ein while
START=1; STOP=10; STEP=3
N=$START
while [ $N -le $STOP ]; do
echo $N
N=$[$N+$STEP]
done
Auch hier durchläuft $N alle Werte von $START bis $STOP in Abständen
von $STEP.
Quellen
1. Manpage
$ man bash
2. Bücher
Learning the Bash Shell,
Newham & Rosenblatt, O’ Reilly
3. Webpages
http://www.gnu.org/software/bash/manual/bashref.html
http://www.linux.se/doc/HOWTO/Adv-Bash-Scr-HOWTO/index.html
http://www.gi.kernel.org/sites/www.linuxdoc.org/LDP/LG/issue25/dearman.html[textonly]
http://www.dcc.unicamp.br/~celio/mc514/bash/bash-tute.html
4. Vorhandene Scripts
/etc/init.d/*
/.bashrc