|
Man kann mit einer until- bzw. mit einer
while-Schleife schnell kleine aber
sehr nützliche Tools schreiben, die einem lästige Aufgaben abnehmen.
Angenommen, bei der Benutzung eines Rechners tritt ein Problem auf, bei
dem nur der Administrator helfen kann. Dann möchte man informiert werden,
sobald dieser an seinem Arbeitsplatz ist. Man kann jetzt in regelmäßigen
Abständen das Kommando who ausführen, und dann in der Ausgabe nach
dem Eintrag root suchen. Das ist aber lästig.
Einfacher geht es, wenn wir uns ein kurzes Skript schreiben, das alle 30
Sekunden automatisch überprüft, ob der Admin angemeldet ist. Wir erreichen
das mit dem folgenden Code:
auf-root-warten.sh
|
#!/bin/sh
until who | grep "^root "
do sleep 30
done
echo Big Brother is watching you!
|
Das Skript führt also so lange das Kommando aus, bis die Ausführung
erfolgreich war. Dabei wird die Ausgabe von
who mit einer Pipe in
das grep-Kommando umgeleitet. Dieses sucht darin nach einem Auftreten
von root am Zeilenanfang. Der Rückgabewert von
grep ist 0 wenn das
Muster gefunden wird, 1 wenn es nicht gefunden wird
und 2 wenn ein Fehler
auftrat. Damit der Rechner nicht die ganze Zeit mit dieser Schleife
beschäftigt ist, wird im Schleifenkörper ein sleep 30 ausgeführt,
um den Prozeß für 30 Sekunden schlafen zu schicken. Sobald der Admin
sich eingeloggt hat, wird eine entsprechende Meldung ausgegeben.
|
Analog zum vorhergehenden Beispiel kann man auch ein Skript schreiben,
das meldet, sobald sich ein Benutzer abgemeldet hat. Dazu ersetzen wir nur
die until-Schleife durch eine entsprechende while-Schleife:
warten-bis-root-verschwindet.sh
|
#!/bin/sh
while who | grep "^root "
do sleep 30
done
echo Die Katze ist aus dem Haus, Zeit, dass die Mäuse tanzen!
|
Die Schleife wird nämlich dann so lange ausgeführt, bis grep einen Fehler
(bzw. eine erfolglose Suche) zurückmeldet.
|
|
Dieses Skript dient dazu, den Apache HTTP-Server zu starten. Es wird
während des Bootvorgangs gestartet, wenn der dazugehörige Runlevel
initialisiert wird.
Das Skript muss mit einem Parameter aufgerufen werden. Möglich sind
hier start, stop,
status, restart und reload. Wenn falsche Parameter
übergeben wurden, wird eine entsprechende Meldung angezeigt.
Das Ergebnis der Ausführung wird mit Funktionen dargestellt, die aus
der Datei /etc/rc.d/init.d/functions stammen. Ebenfalls in dieser Datei
sind Funktionen, die einen Dienst starten oder stoppen.
Zunächst wird festgelegt, dass dieses Skript in der Bourne-Shell ausgeführt
werden soll ( Auswahl der Shell).
Dann folgen Kommentare, die den Sinn des Skriptes erläutern.
beispiel.sh (Fortsetzung)
|
## Startup script for the Apache Web Server
#
# chkconfig: 345 85 15
# description: Apache is a World Wide Web server. It is \
# used to serve HTML files and CGI
#
# processname: httpd
# pidfile: /var/run/httpd.pid
# config: /etc/httpd/conf/access.conf
# config: /etc/httpd/conf/httpd.conf
# config: /etc/httpd/conf/srm.conf
|
Jetzt wird die Datei mit den Funktionen eingebunden.
beispiel.sh (Fortsetzung)
|
# Source function library.
/etc/rc.d/init.d/functions
|
Hier werden die Aufrufparameter ausgewertet.
beispiel.sh (Fortsetzung)
|
# See how we were called.
case "$1" in
start)
echo -n "Starting httpd: "
|
Nachdem eine Meldung über den auszuführenden Vorgang ausgegeben
wurde, wird die Funktion daemon aus der Funktionsbibliothek ausgeführt.
Diese Funktion startet das Programm, dessen Name hier als Parameter
übergeben wird. Dann gibt sie eine Meldung über den Erfolg aus.
beispiel.sh (Fortsetzung)
|
daemon httpd
echo
|
Jetzt wird ein Lock-File angelegt. (Ein Lock-File signalisiert anderen
Prozessen, dass ein bestimmter Prozeß bereits gestartet ist. So kann ein
zweiter Aufruf verhindert werden.)
beispiel.sh (Fortsetzung)
|
touch /var/lock/subsys/httpd
;;
stop)
echo -n "Shutting down http: "
|
Hier passiert im Prinzip das gleiche wie oben, nur dass mit der Funktion
killproc der Daemon angehalten wird.
beispiel.sh (Fortsetzung)
|
killproc httpd
echo
|
Danach werden Lock-File und PID-File gelöscht. (In einem sogenannten PID-File
hinterlegen einige Prozesse ihre Prozeß-ID, um anderen Programmen den
Zugriff zu erleichtern, z.B. um den Prozeß anzuhalten etc.)
beispiel.sh (Fortsetzung)
|
rm -f /var/lock/subsys/httpd
rm -f /var/run/httpd.pid
;;
status)
|
Die Funktion status stellt fest, ob der entsprechende Daemon bereits läuft,
und gibt das Ergebnis aus.
beispiel.sh (Fortsetzung)
|
status httpd
;;
restart)
|
Bei Aufruf mit dem Parameter restart ruft sich das Skript zwei mal selbst
auf (in $0 steht der Aufrufname des laufenden Programms). Einmal, um
den Daemon zu stoppen, dann, um ihn wieder zu starten.
beispiel.sh (Fortsetzung)
|
$0 stop
$0 start
;;
reload)
echo -n "Reloading httpd: "
|
Hier sendet die killproc-Funktion dem Daemon ein Signal das ihm sagt,
dass er seine Konfiguration neu einlesen soll.
beispiel.sh (Fortsetzung)
|
killproc httpd -HUP
echo
;;
*)
echo "Usage: $0 {start|stop|restart|reload|status}"
|
Bei Aufruf mit einem beliebigen anderen Parameter wird eine Kurzhilfe
ausgegeben. Dann wird dafür gesorgt, dass das Skript mit dem Exit-Code 1
beendet wird. So kann festgestellt werden, ob das Skript ordnungsgemäß
beendet wurde ( exit).
beispiel.sh (Fortsetzung)
|
exit 1
esac
exit 0
|
|
Es kommt in der Praxis sehr oft vor, dass man ein Skript schreibt, dem der
Anwender Parameter übergeben soll. Wenn das nur eine Kleinigkeit ist (zum
Beispiel ein Dateiname), dann fragt man einfach die entsprechenden
vordefinierten Variablen ab.
Sollen aber richtige Parameter
eingesetzt werden, die sich so einsetzen lassen wie man es von vielen
Kommandozeilentools gewohnt ist, dann benutzt man das Hilfsprogramm
getopt. Dieses Programm parst die originalen
Parameter und gibt sie in standardisierter Form zurück.
Das soll an folgendem Skript verdeutlicht werden. Das Skript kennt die
Optionen -a und -b. Letzterer Option muss ein zusätzlicher Wert mitgegeben
werden. Alle anderen Parameter werden als Dateinamen interpretiert.
getopt.sh
|
#!/bin/sh
set -- `getopt "ab:" "$@"` || {
|
Das set-Kommando belegt den Inhalt der
vordefinierten Variablen
neu, so dass es aussieht, als ob dem Skript die Rückgabewerte von getopt
übergeben wurden. Man muss die beiden Minuszeichen angeben, da sie
dafür sorgen, dass die Aufrufparameter an getopt und nicht an die Shell
selbst übergeben werden. Die originalen Parameter werden von getopt
untersucht und modifiziert zurückgegeben: a und b werden als Parameter
Markiert, b sogar mit der Möglichkeit einer zusätzlichen Angabe.
Wenn dieses Kommando fehlschlägt ist das ein Zeichen dafür, dass falsche
Parameter übergeben wurden. Also wird nach einer entsprechenden
Meldung das Programm mit Exit-Code 1 verlassen.
getopt.sh (Fortsetzung)
|
echo "Anwendung: `basename $0` [-a] [-b Name] Dateien" 1>&2
exit 1
}
echo "Momentan steht in der Kommandozeile folgendes: $*"
aflag=0 name=NONE
while :
do
|
In einer Endlos-Schleife, die man mit Hilfe des Null-Befehls (:) baut,
werden die neuen Parameter der Reihe nach untersucht. Wenn ein -a
vorkommt, wird die Variable aflag gesetzt. Bei einem -b werden per shift
alle Parameter nach Links verschoben, dann wird der Inhalt des nächsten
Parameters in der Variablen name gesichert.
getopt.sh (Fortsetzung)
|
case "$1" in
-a) aflag=1 ;;
-b) shift; name="$1" ;;
--) break ;;
|
Wenn ein -- erscheint, ist das ein Hinweis darauf, dass die Liste der
Parameter abgearbeitet ist. Dann wird per
break) die Endlosschleife
unterbrochen. Die Aufrufparameter enthalten jetzt nur noch die eventuell
angegebenen Dateinamen, die von dem restlichen Skript wie gewohnt
weiterverarbeitet werden können.
getopt.sh (Fortsetzung)
|
esac
shift
done
shift
|
Am Ende werden die Feststellungen ausgegeben.
getopt.sh (Fortsetzung)
|
echo "aflag=$aflag / Name = $name / Die Dateien sind $*"
|
|
Ein laufendes Shell-Skript kann durch Druck auf die Interrupt-Taste
(normalerweise [
CTRL+C
]) unterbrochen werden. Durch Druck auf diese Taste
wird ein Signal an den entsprechenden Prozeß gesandt, das ihn bittet sich
zu beenden. Dieses Signal heißt SIGINT (für SIGnal INTerrupt) und trägt
die Nummer 2. Das kann ein kleines Problem darstellen, wenn das Skript
sich temporäre Dateien angelegt hat, da diese nach der Ausführung nur noch
unnötig Platz verbrauchen und eigentlich gelöscht werden sollten. Man kann
sich sicher auch noch wichtigere Fälle vorstellen, in denen ein Skript
bestimmte Aufgaben auf jeden Fall erledigen muss, bevor es sich beendet.
Es gibt eine Reihe weiterer Signale, auf die ein Skript reagieren kann. Alle
sind in der Man-Page von signal beschrieben. Hier die wichtigsten:
Nummer |
Name |
Bedeutung |
0 |
Normal Exit |
Wird durch das exit-Kommando ausgelöst. |
1 |
SIGHUP |
Wenn die Verbindung abbricht (z.B. wenn das Terminal
geschlossen wird).
|
2 |
SIGINT |
Zeigt einen Interrupt an ([
CTRL+C
]).
|
15 |
SIGTERM |
Wird vom kill-Kommando gesendet.
|
Wie löst man jetzt dieses Problem? Glücklicherweise verfügt die Shell über
das trap-Kommando, mit dessen Hilfe man auf diese Signale reagieren kann.
Die Anwendung soll in folgendem Skript beispielhaft dargestellt werden.
Das Skript soll eine komprimierte Textdatei mittels zcat in ein temporäres
File entpacken, dieses mit pg seitenweise anzeigen und nachher wieder
löschen.
zeige-komprimierte-datei.sh
|
#!/bin/sh
stat=1
temp=/tmp/zeige$$
|
Zunächst werden zwei Variablen belegt, die im weiteren Verlauf benutzt
werden sollen. In stat wird der Wert abgelegt, den das Skript Falle
eines Abbruchs als Exit-Status zurückliefern soll. Die Variable temp enthält
den Namen für eine temporäre Datei. Dieser setzt sich zusammen aus
/tmp/zeige und der Prozeßnummer des laufenden Skripts. So soll
sichergestellt werden, dass noch keine Datei mit diesem Namen existiert.
zeige-komprimierte-datei.sh (Fortsetzung)
|
trap 'rm -f $temp; exit $stat' 0
trap 'echo "`basename $0`: Ooops..." 1>&2' 1 2 15
|
Hier werden die Traps definiert. Bei Signal 0 wird die temporäre Datei
gelöscht und der Wert aus der Variable stat als Exit-Code zurückgegeben.
Dabei wird dem rm-Kommando der Parameter -f mitgegeben, damit keine
Fehlermeldung ausgegeben wird, falls die Datei (noch) nicht existiert.
Dieser Fall tritt bei jedem Beenden des Skriptes auf, also sowohl bei einem
normalen Ende, als auch beim Exit-Kommando, bei einem Interrupt oder
bei einem Kill. Der zweite Trap
reagiert auf die Signale 1, 2 und 15. Das
heißt, er wird bei jedem unnormalen Ende ausgeführt. Er gibt eine
entsprechende Meldung auf die Standard-Fehler-Ausgabe aus.
Danach wird das Skript beendet, und der erste Trap wird ausgeführt.
zeige-komprimierte-datei.sh (Fortsetzung)
|
case $# in
1) zcat "$1" > $temp
pg $temp
stat=0
;;
|
Jetzt kommt die eigentliche Funktionalität des Skriptes: Das
case-Kommando ( case) testet die Anzahl der übergebenen Parameter.
Wenn genau ein Parameter übergeben wurde, entpackt zcat die Datei, die
im ersten Parameter angegeben wurde, in die temporäre Datei. Dann folgt
die seitenweise Ausgabe mittels pg. Nach Beendigung der Ausgabe wird der
Status in der Variablen auf 0 gesetzt, damit beim Skriptende der korrekte
Exit-Code zurückgegeben wird.
zeige-komprimierte-datei.sh (Fortsetzung)
|
*) echo "Anwendung: `basename $0` Dateiname" 1gt;&2
esac
|
Wenn case eine andere Parameterzahl feststellt, wird eine Meldung mit der
Aufrufsyntax auf die Standard-Fehlerausgabe geschrieben.
|
|
|