» SelfLinux » Programmierung » Shellprogrammierung » Abschnitt 5 SelfLinux-0.12.3
zurück Startseite Kapitelanfang Inhaltsverzeichnis PDF-Download (109 KB) GFDL weiter

SelfLinux-Logo
Dokument Shellprogrammierung  Autor
 Formatierung
 GFDL
 

5 Anhang A: Beispiele


5.1 Schleifen und Rückgabewerte

Man kann mit einer  until- bzw. mit einer  while-Schleife schnell kleine aber sehr nützliche Tools schreiben, die einem lästige Aufgaben abnehmen.


5.1.1 Schleife, bis ein Kommando erfolgreich war

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.


5.1.2 Schleife, bis ein Kommando nicht erfolgreich war

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.


5.2 Ein typisches Init-Skript

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

beispiel.sh
#!/bin/sh
     

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
     

5.3 Parameterübergabe in der Praxis

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 $*"
     

5.4 Fallensteller: Auf Traps reagieren

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.



zurück Seitenanfang Startseite Kapitelanfang Inhaltsverzeichnis PDF-Download (109 KB) GFDL weiter