325 lines
16 KiB
TeX
325 lines
16 KiB
TeX
\chapter{Beispiele}
|
|
|
|
|
|
\section{Schleifen und Rückgabewerte}\label{beisp_schleifen_exitcode}\index{Schleife}\index{Rückgabewert}
|
|
|
|
Man kann mit einer \texttt{until}\index{until=\texttt{until}}- bzw. mit einer \texttt{while}\index{while=\texttt{while}}-Schleife schnell kleine aber sehr nützliche Tools schreiben, die einem lästige Aufgaben abnehmen.
|
|
|
|
|
|
\subsection{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 \texttt{who}\index{who=\texttt{who}} ausführen, und dann in der Ausgabe nach dem Eintrag `\texttt{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:
|
|
|
|
\footnotesize
|
|
\index{\^=\texttt{\^}}\index{Anführungszeichen}\index{Pipe}\index{grep=\texttt{grep}}\index{sleep=\texttt{sleep}}\index{who=\texttt{who}}
|
|
\begin{listing}[2]{1}
|
|
#!/bin/sh
|
|
until who | grep "^root "
|
|
do sleep 30
|
|
done
|
|
echo Big Brother is watching you!
|
|
\end{listing}
|
|
\normalsize
|
|
|
|
Das Skript führt also so lange das Kommando aus, bis die Ausführung erfolgreich war. Dabei wird die Ausgabe von \texttt{who}\index{who=\texttt{who}} mit einer Pipe (\ref{datenstrom}) in das \texttt{grep}\index{grep=\texttt{grep}}-Kommando umgeleitet. Dieses sucht darin nach einem Auftreten von `\texttt{root~}' am Zeilenanfang. Der Rückgabewert von \texttt{grep}\index{grep=\texttt{grep}} ist 0 wenn das Muster\index{Mustererkennung} 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 \texttt{sleep 30}\index{sleep=\texttt{sleep}} ausgeführt, um den Prozeß für 30 Sekunden schlafen zu schicken. Sobald der Admin sich eingeloggt hat, wird eine entsprechende Meldung ausgegeben.
|
|
|
|
|
|
\subsection{Schleife, bis ein Kommando erfolglos war}
|
|
|
|
Analog zum vorhergehenden Beispiel kann man auch ein Skript schreiben, das meldet, sobald sich ein Benutzer abgemeldet hat. Dazu ersetzen wir nur die \texttt{until}- Schleife durch eine entsprechende \texttt{while}-Schleife:
|
|
|
|
\footnotesize
|
|
\index{\^=\texttt{\^}}\index{Anführungszeichen}\index{grep=\texttt{grep}}\index{Pipe}\index{sleep=\texttt{sleep}}\index{who=\texttt{who}}
|
|
\begin{listing}[2]{1}
|
|
#!/bin/sh
|
|
while who | grep "^root "
|
|
do sleep 30
|
|
done
|
|
echo Die Katze ist aus dem Haus, Zeit, daß die Mäuse tanzen!
|
|
\end{listing}
|
|
\normalsize
|
|
|
|
Die Schleife wird nämlich dann so lange ausgeführt, bis \texttt{grep}\index{grep=\texttt{grep}} einen Fehler (bzw. eine erfolglose Suche) zurückmeldet.
|
|
|
|
|
|
\section{Eine Datei zeilenweise bearbeiten}
|
|
|
|
\texttt{cat datei.txt | while read i}
|
|
|
|
TODO!!!
|
|
|
|
Achtung! while ist eine Subshell - Daten müssen hochgereicht werden.
|
|
|
|
\section{Ein typisches Init-Skript}\label{init-skript}\index{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 muß mit einem Parameter\index{Parameter} aufgerufen werden. Möglich sind hier \textsl{start}, \textsl{stop}, \textsl{status}, \textsl{restart} und \textsl{reload}. Wenn falsche Parameter\index{Parameter} übergeben wurden, wird eine entsprechende Meldung angezeigt.
|
|
|
|
Das Ergebnis der Ausführung wird mit Funktionen\index{Funktion} dargestellt, die aus der Datei \texttt{/etc/rc.d/init.d/functions} stammen. Ebenfalls in dieser Datei sind Funktionen, die einen Dienst starten oder stoppen.
|
|
|
|
\begin{flushright}
|
|
Zunächst wird festgelegt, daß dieses Skript in der Bourne-Shell ausgeführt werden soll (\ref{auswahl_der_shell}).
|
|
\end{flushright}
|
|
\footnotesize
|
|
\begin{listing}[2]{1}
|
|
#!/bin/sh
|
|
\end{listing}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Dann folgen Kommentare\index{Kommentar}, die den Sinn des Skriptes erläutern (\ref{kommentare}).
|
|
\end{flushright}
|
|
\footnotesize
|
|
\begin{listingcont}
|
|
#
|
|
# 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
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Jetzt wird die Datei mit den Funktionen\index{Funktion} eingebunden (\ref{source}).
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{source=\texttt{source}}
|
|
\begin{listingcont}
|
|
# Source function library.
|
|
. /etc/rc.d/init.d/functions
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Hier werden die Aufrufparameter ausgewertet (\ref{case}).
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}\index{case=\texttt{case}}
|
|
\begin{listingcont}
|
|
# See how we were called.
|
|
case "$1" in
|
|
start)
|
|
echo -n "Starting httpd: "
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Nachdem eine Meldung über den auszuführenden Vorgang ausgegeben wurde, wird die Funktion \texttt{daemon} aus der Funktionsbibliothek ausgeführt. Diese Funktion startet das Programm, dessen Name hier als Parameter\index{Parameter} übergeben wird. Dann gibt sie eine Meldung über den Erfolg aus.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\begin{listingcont}
|
|
daemon httpd
|
|
echo
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Jetzt wird ein Lock-File\footnote{Ein Lock-File signalisiert anderen Prozessen, daß ein bestimmter Prozeß bereits gestartet ist. So kann ein zweiter Aufruf verhindert werden.} angelegt.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{Anführungszeichen}\index{touch=\texttt{touch}}
|
|
\begin{listingcont}
|
|
touch /var/lock/subsys/httpd
|
|
;;
|
|
stop)
|
|
echo -n "Shutting down http: "
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Hier passiert im Prinzip das gleiche wie oben, nur daß mit der Funktion \texttt{killproc} der Daemon angehalten wird.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\begin{listingcont}
|
|
killproc httpd
|
|
echo
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Danach werden Lock-File und PID-File\footnote{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).} gelöscht.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\begin{listingcont}
|
|
rm -f /var/lock/subsys/httpd
|
|
rm -f /var/run/httpd.pid
|
|
;;
|
|
status)
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Die Funktion \texttt{status} stellt fest, ob der entsprechende Daemon bereits läuft, und gibt das Ergebnis aus.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\begin{listingcont}
|
|
status httpd
|
|
;;
|
|
restart)
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Bei Aufruf mit dem Parameter\index{Parameter} \textsl{restart} ruft sich das Skript zwei mal selbst auf (in \texttt{\$0} steht der Aufrufname des laufenden Programms). Einmal, um den Daemon zu stoppen, dann, um ihn wieder zu starten.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}
|
|
\begin{listingcont}
|
|
$0 stop
|
|
$0 start
|
|
;;
|
|
reload)
|
|
echo -n "Reloading httpd: "
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Hier sendet die \texttt{killproc}-Funktion dem Daemon ein Signal\index{Signal} das ihm sagt, daß er seine Konfiguration neu einlesen soll.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}
|
|
\begin{listingcont}
|
|
killproc httpd -HUP
|
|
echo
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {start|stop|restart|reload|status}"
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Bei aufruf mit einem beliebigen anderen Parameter\index{Parameter} wird eine Kurzhilfe ausgegeben. Dann wird dafür gesorgt, daß das Skript mit dem Exit-Code 1 beendet wird. So kann festgestellt werden, ob das Skript ordnungsgemäß beendet wurde (\ref{exit}).
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{exit=\texttt{exit}}
|
|
\begin{listingcont}
|
|
exit 1
|
|
esac
|
|
|
|
exit 0
|
|
\end{listingcont}
|
|
\normalsize
|
|
|
|
|
|
\section{Parameterübergabe in der Praxis}\label{beisp_parameter}\index{Parameter}
|
|
|
|
Es kommt in der Praxis sehr oft vor, daß man ein Skript schreibt, dem derAnwender Parameter\index{Parameter} übergeben soll. Wenn das nur eine Kleinigkeit ist (zum Beispiel ein Dateiname), dann fragt man einfach die entsprechenden vordefinierten Variablen (\ref{vordefinierte_variablen}) ab. Sollen aber `richtige' Parameter\index{Parameter} eingesetzt werden, die sich so einsetzen lassen wie man es von vielen Kommandozeilentools gewohnt ist, dann benutzt man das Hilfsprogramm \texttt{getopt}\label{getopt}\index{getopt=\texttt{getopt}}. Dieses Programm parst die originalen Parameter\index{Parameter} und gibt sie in `standardisierter' Form zurück.
|
|
|
|
Das soll an folgendem Skript verdeutlicht werden. Das Skript kennt die Optionen \texttt{-a} und \texttt{-b}. Letzterer Option muß ein zusätzlicher Wert mitgegeben werden. Alle anderen Parameter\index{Parameter} werden als Dateinamen interpretiert.
|
|
|
|
\footnotesize
|
|
\index{\$@=\texttt{\$@}}\index{Anführungszeichen}\index{Backticks}\index{!|!|=\texttt{!|!|}}\index{getopt=\texttt{getopt}}\index{OR}\index{set=\texttt{set}}
|
|
\begin{listing}[2]{1}
|
|
#!/bin/sh
|
|
set -- `getopt "ab:" "$@"` || {
|
|
\end{listing}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Das \texttt{set}\index{set=\texttt{set}}-Kommando belegt den Inhalt der vordefinierten Variablen (\ref{vordefinierte_variablen}) neu, so daß es aussieht, als ob dem Skript die Rückgabewerte von \texttt{getopt}\index{getopt=\texttt{getopt}} übergeben wurden. Man muß die beiden Minuszeichen angeben, da sie dafür sorgen, daß die Aufrufparameter an \texttt{getopt}\index{getopt=\texttt{getopt}} und nicht an die Shell selbst übergeben werden. Die originalen Parameter\index{Parameter} werden von \texttt{getopt}\index{getopt=\texttt{getopt}} untersucht und modifiziert zurückgegeben: \texttt{a} und \texttt{b} werden als Parameter\index{Parameter} Markiert, \texttt{b} sogar mit der Möglichkeit einer zusätzlichen Angabe.
|
|
|
|
Wenn dieses Kommando fehlschlägt ist das ein Zeichen dafür, daß falsche Parameter\index{Parameter} übergeben wurden. Also wird nach einer entsprechenden Meldung das Programm mit Exit-Code 1 verlassen.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{\$n=\texttt{\$}$n$}\index{Null-Befehl}\index{!>\&=\texttt{!>\&}}\index{!==\texttt{!=}}\index{Anführungszeichen}\index{Backticks}\index{basename=\texttt{basename}}
|
|
\begin{listingcont}
|
|
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
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
In einer Endlosschleife\index{Endlosschleife}, die man mit Hilfe des Null-Befehls (\texttt{:}, \ref{null-befehl}) baut, werden die `neuen' Parameter\index{Parameter} der Reihe nach untersucht. Wenn ein \texttt{-a} vorkommt, wird die Variable \texttt{aflag} gesetzt. Bei einem \texttt{-b} werden per \texttt{shift}\index{shift=\texttt{shift}} alle Parameter\index{Parameter} nach Links verschoben, dann wird der Inhalt des nächsten Parameters\index{Parameter} in der Variablen \texttt{name} gesichert.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{!==\texttt{!=}}\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}\index{case=\texttt{case}}\index{shift=\texttt{shift}}
|
|
\begin{listingcont}
|
|
case "$1" in
|
|
-a) aflag=1 ;;
|
|
-b) shift; name="$1" ;;
|
|
--) break ;;
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Wenn ein \texttt{-{}-} erscheint, ist das ein Hinweis darauf, daß die Liste der Parameter\index{Parameter} abgearbeitet ist. Dann wird per \texttt{break}\index{break=\texttt{break}} (\ref{break}) die Endlosschleife unterbrochen. Die Aufrufparameter enthalten jetzt nur noch die eventuell angegebenen Dateinamen, die jetzt von dem Restlichen Skript wie gewohnt weiterverarbeitet werden können.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{shift=\texttt{shift}}
|
|
\begin{listingcont}
|
|
esac
|
|
shift
|
|
done
|
|
shift
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Am Ende werden die Feststellungen ausgegeben.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{\$*=\texttt{\$*}}\index{Anführungszeichen}
|
|
\begin{listingcont}
|
|
echo "aflag=$aflag / Name = $name / Die Dateien sind $*"
|
|
\end{listingcont}
|
|
\normalsize
|
|
|
|
|
|
\section{Fallensteller: Auf Traps
|
|
reagieren}\label{traps}\index{trap=\texttt{trap}|(}\index{Signal|(}
|
|
|
|
Ein laufendes Shell-Skript kann durch Druck auf die Interrupt-Taste (normalerweise \Ovalbox{CTRL}-\Ovalbox{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 muß, bevor es sich beendet.
|
|
|
|
Es gibt eine Reihe weiterer Signale, auf die ein Skript reagieren kann. Alle sind in der Man-Page von \texttt{signal} beschrieben. Hier die wichtigsten:\nopagebreak
|
|
|
|
\LTXtable{\textwidth}{tab_signale.tex}
|
|
|
|
Wie löst man jetzt dieses Problem? Glücklicherweise verfügt die Shell über das \texttt{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 \texttt{zcat} in ein temporäres File entpacken, dieses mit \texttt{pg} seitenweise anzeigen und nachher wieder löschen.
|
|
|
|
\footnotesize
|
|
\index{!==\texttt{!=}}
|
|
\begin{listing}[2]{1}
|
|
#!/bin/sh
|
|
stat=1
|
|
temp=/tmp/zeige$$
|
|
\end{listing}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Zunächst werden zwei Variablen belegt, die im weiteren Verlauf benutzt werden sollen. In \texttt{stat} wird der Wert abgelegt, den das Skript im Falle eines Abbruchs als Exit-Status zurückliefern soll. Die Variable \texttt{temp} enthält den Namen für eine temporäre Datei. Dieser setzt sich zusammen aus \texttt{/tmp/zeige} und der Prozeßnummer des laufenden Skripts. So soll sichergestellt werden, daß noch keine Datei mit diesem Namen existiert.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{Ticks}\index{!>\&=\texttt{!>\&}}\index{\$n=\texttt{\$}$n$}\index{Ticks}\index{Anführungszeichen}\index{Backticks}\index{basename=\texttt{basename}}
|
|
\begin{listingcont}
|
|
trap 'rm -f $temp; exit $stat' 0
|
|
trap 'echo "`basename $0`: Ooops..." 1>&2' 1 2 15
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Hier werden die Traps definiert. Bei Signal 0 wird die temporäre Datei gelöscht und der Wert aus der Variable \texttt{stat} als Exit-Code zurückgegeben. Dabei wird dem \texttt{rm}-Kommando der Parameter\index{Parameter} \texttt{-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\index{kill=\texttt{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-Fehlerausgabe (\ref{datenstrom}) aus. Danach wird das Skript beendet, und der erste Trap wird ausgeführt.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{\$\#=\texttt{\$\#}}\index{!==\texttt{!=}}\index{!>=\texttt{!>}}\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}\index{case=\texttt{case}}
|
|
\begin{listingcont}
|
|
case $# in
|
|
1) zcat "$1" > $temp
|
|
pg $temp
|
|
stat=0
|
|
;;
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Jetzt kommt die eigentliche Funktionalität des Skriptes: Das \texttt{case}-Kommando (\ref{case}) testet die Anzahl der übergebenen Parameter\index{Parameter}. Wenn genau ein Parameter\index{Parameter} übergeben wurde, entpackt \texttt{zcat} die Datei, die im ersten Parameter\index{Parameter} angegeben wurde, in die temporäre Datei. Dann folgt die Seitenweise Ausgabe mittels \texttt{pg}. Nach Beendigung der Ausgabe wird der Status in der Variablen auf 0 gesetzt, damit beim Skriptende der korrekte Exit-Code zurückgegeben wird.
|
|
\end{flushright}
|
|
\footnotesize
|
|
\index{!>\&=\texttt{!>\&}}\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}\index{Backticks}\index{basename=\texttt{basename}}
|
|
\begin{listingcont}
|
|
*) echo "Anwendung: `basename $0` Dateiname" 1>&2
|
|
esac
|
|
\end{listingcont}
|
|
\normalsize
|
|
\begin{flushright}
|
|
Wenn \texttt{case} eine andere Parameterzahl feststellt, wird eine Meldung mit der Aufrufsyntax auf die Standard-Fehlerausgabe geschrieben.
|
|
\end{flushright}
|
|
\index{trap=\texttt{trap}|)}\index{Signal|)}
|