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