442 lines
18 KiB
TeX
442 lines
18 KiB
TeX
% $Id$
|
|
\chapter{Schmutzige Tricks :-)}
|
|
|
|
Eigentlich sind diese Tricks gar nicht so schmutzig. Hier ist lediglich eine
|
|
Sammlung von Beispielen, die genial einfach oder genial gut programmiert sind.
|
|
Das bedeutet nicht, daß jeder Shell-Programmierer diese Techniken benutzen
|
|
sollte. Ganz im Gegenteil. Einige Mechanismen bergen Gefahren, die nicht auf
|
|
den ersten Blick erkennbar sind.
|
|
|
|
Mit anderen Worten: \textbf{Wenn Du diese Techniken nicht verstehst, dann
|
|
benutze sie nicht!}
|
|
|
|
Die Intention hinter diesem Abschnitt ist es, dem gelangweilten Skripter etwas
|
|
interessantes zum Lesen zu geben. Das inspiriert dann vielleicht dazu, doch
|
|
einmal in den fortgeschrittenen Bereich vorzustoßen, neue Techniken
|
|
kennenzulernen. Außerdem kann das Wissen über gewisse Techniken eine große
|
|
Hilfe beim Lesen fremder Skripte darstellen, die eventuell von diesen Techniken
|
|
Gebrauch machen.
|
|
|
|
Diese Techniken sind nicht `auf meinem Mist gewachsen', sie stammen vielmehr
|
|
aus Skripten, die mir im Laufe der Zeit in die Finger gekommen sind. Ich danke
|
|
an dieser Stelle den klugen Köpfen, die sich solche Sachen einfallen lassen
|
|
haben.
|
|
|
|
\section{Die Tar-Brücke}
|
|
|
|
Eine sogenannte Tar-Brücke benutzt man, wenn eine oder mehrere Dateien zwischen
|
|
Rechnern übertragen werden sollen, aber kein Dienst wie SCP oder FTP zur
|
|
Verfügung steht. Außerdem hat die Methode den Vorteil, daß Benutzerrechte und
|
|
andere Dateiattribute bei der Übertragung erhalten
|
|
bleiben\footnote{Vorausgesetzt natürlich, daß der Benutzer auf der entfernten
|
|
Seite über die nötigen Rechte verfügt.}.
|
|
|
|
Der Trick besteht darin, auf einer Seite der Verbindung etwas mit \texttt{tar}
|
|
einzupacken, dies durch eine Pipe auf die andere Seite der Verbindung zu
|
|
bringen und dort wieder zu entpacken.
|
|
|
|
Wenn dem Kommando \texttt{tar} an Stelle eines Dateinamens ein Minus-Zeichen
|
|
als Archiv gegeben wird, benutzt es~--~je nach der gewählten Aktion~--~die
|
|
Standard-Ein- bzw. -Ausgabe. Diese kann an ein weiteres \texttt{tar} übergeben
|
|
werden um wieder entpackt zu werden.
|
|
|
|
Ein Beispiel verdeutlicht diese Kopier-Fähigkeit:
|
|
|
|
\texttt{tar cf - . | ( cd /tmp/backup; tar xf - )}
|
|
|
|
Hier wird zunächst der Inhalt des aktuellen Verzeichnisses `verpackt'. Das
|
|
Resultat wird an die Standard-Ausgabe geschrieben. Auf der Empfängerseite der
|
|
Pipe wird eine Subshell geöffnet. Das ist notwendig, da das empfangende
|
|
\texttt{tar} in einem anderen Verzeichnis laufen soll. In der Subshell wird
|
|
zunächst das Verzeichnis gewechselt. Dann liest ein \texttt{tar} von der
|
|
Standard-Eingabe und entpackt alles was er findet. Sobald keine Eingaben mehr
|
|
kommen, beendet sich der Prozeß mitsamt der Subshell.
|
|
|
|
Am Ziel-Ort finden sich jetzt die gleichen Dateien wie am Quell-Ort.
|
|
|
|
Das ließe sich lokal natürlich auch anders lösen. Man könnte erst ein Archiv
|
|
erstellen, das dann an anderer Stelle wieder auspacken. Nachteil: Es muß
|
|
genügend Platz für das Archiv vorhanden sein. Denkbar wäre auch ein in den Raum
|
|
gestelltes \texttt{cp -Rp * /tmp/backup}. Allerdings fehlen einem dabei
|
|
mitunter nützliche \texttt{tar}-Optionen\footnote{Mit \texttt{-l} verläßt
|
|
\texttt{tar} beispielsweise nicht das File-System. Nützlich wenn eine Partition
|
|
gesichert werden soll.}, und die oben erwähnte Brücke wäre mit einem reinen
|
|
\texttt{cp} nicht möglich.
|
|
|
|
Eine Seite der Pipe kann nämlich auch ohne Probleme auf einem entfernten
|
|
Rechner `stattfinden'. Kommandos wie \texttt{ssh} oder \texttt{rsh} (letzteres
|
|
nur unter Vorsicht einsetzen!) schlagen die Brücke zu einem anderen System,
|
|
dort wird entweder gepackt und versendet oder quasi die Subshell gestartet und
|
|
gelesen. Das sieht wie folgt aus:
|
|
|
|
\texttt{ssh 192.168.2.1 tar clf - / | (cd /mnt/backup; tar xf - )}
|
|
|
|
Hier wird auf einem entfernten Rechner die Root-Partition verpackt, per SSH in
|
|
das lokale System geholt und lokal im Backup-Verzeichnis entpackt.
|
|
|
|
Der Weg in die andere Richtung ist ganz ähnlich:
|
|
|
|
\texttt{tar cf - datei.txt | ssh 192.168.2.1 \dq(mkdir -p \$PWD ;cd \$PWD; tar xf -)\dq}
|
|
|
|
Hier wird die Datei verpackt und versendet. Eine Besonderheit gegenüber dem
|
|
vorigen Beispiel bestehtdarin, daß das Zielverzeichnis bei Bedarf erstellt
|
|
wird, bevor die Datei dort entpackt wird. Zur Erklärung: Die Variable
|
|
\texttt{\$PWD} wird, da sie nicht von Ticks `gesichert' wird, schon lokal durch
|
|
die Shell expandiert. An dieser Stelle erscheint also auf dem entfernten System
|
|
der Name des aktuellen Verzeichnisses auf dem lokalen System.
|
|
|
|
|
|
\section{Binaries inside}
|
|
|
|
Software wird meistens in Form von Paketen verteilt. Entweder handelt es sich
|
|
dabei um auf das Betriebssystem abgestimmte Installationspakete (rpm, deb, pkg
|
|
usw.), gepackte Archive (zip, tgz) oder Installationsprogramme. Unter
|
|
Unix-Systemen bietet sich für letztere die Shell als Trägersystem an.
|
|
Shell-Skripte sind mit wenigen Einschränkungen plattformunabhängig, sie können
|
|
also ohne vorherige Installations- oder Compilier-Arbeiten gestartet werden und
|
|
die Umgebung für das zu installierende Programm testen und / oder vorbereiten.
|
|
|
|
Abgesehen davon können Skripte mit den hier vorgestellten Techniken auch andere
|
|
Daten, z. B. Bilder oder Töne, enthalten.
|
|
|
|
Doch wie werden die~--~üblicherweise binären~--~Pakete auf das Zielsystem
|
|
gebracht?
|
|
|
|
Im Prinzip gibt es dafür zwei unterschiedliche Verfahren:
|
|
|
|
\subsection{Binäre Here-Dokumente}
|
|
\index{Here-Dokument}
|
|
|
|
Eine Möglichkeit ist es, die binäre Datei in Form eines Here-Dokuments
|
|
mitzuliefern. Da es aber in der Natur einer binären Datei liegt nicht-druckbare
|
|
Zeichen zu enthalten, kann die Datei mit Hilfe des Tools \texttt{uuencode}
|
|
vorbereitet werden. Das Tool codiert Eingabedateien so, daß sie nur noch
|
|
einfache Textzeichen enthalten.
|
|
|
|
Sehen wir uns das folgende einfache Beispiel an. Es ist etwas wild konstruiert
|
|
und nicht sehr sinnvoll, aber es zeigt das Prinzip.
|
|
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
|
|
echo "Das Bild wird ausgepackt..."
|
|
|
|
uudecode << 'EOF'
|
|
begin 644 icon.png
|
|
MB5!.1PT*&@H````-24A$4@```!8````6"`8```#$M&P[````"7!(67,```L3
|
|
M```+$P$`FIP8````!&=!34$``+&.?/M1DP```"!C2%)-``!Z)0``@(,``/G_
|
|
\end{lstlisting}
|
|
|
|
Nach einem Hinweis wird also das Here-Dokument als Eingabe für das Tool
|
|
\texttt{uudecode} benutzt. Erstellt wurde das Dokument mit einer Zeile in der
|
|
Form \texttt{uuencode icon.png icon.png}.
|
|
|
|
Wie man sieht ist der Name der Datei in dem Here-Dokument enthalten. Die Datei
|
|
wird entpackt und unter diesem gespeichert. In der `realen Welt' muß an der
|
|
Stelle auf jeden Fall sichergestellt werden, daß keine existierenden Dateien
|
|
versehentlich überschrieben werden.
|
|
|
|
Um diesen Abschnitt nicht allzu lang werden zu lassen überspringen wir einen
|
|
Teil der Datei.
|
|
|
|
\begin{lstlisting}[firstnumber=38]
|
|
M#-""F4%,@%4.GUZ``"(*`6VW6!S#\>C_?/;__Q<R_S]<P/F7AXDA'I\>@``B
|
|
K!>E;2S-,]5!A7`,,U'0@GQ6?8H```P`#@&?)O'P'L0````!)14Y$KD)@@@``
|
|
`
|
|
end
|
|
EOF
|
|
|
|
if [ $? -ne 0 ]; then
|
|
echo "Fehler beim Auspacken der Datei"
|
|
exit 1
|
|
fi
|
|
|
|
display icon.png
|
|
\end{lstlisting}
|
|
|
|
Nach dem Entpacken wird noch der Exit-Code von \texttt{uudecode} überprüft und
|
|
im Fehlerfall eine Ausgabe gemacht. Im Erfolgsfall wird das Bild mittels
|
|
\texttt{display} angezeigt.
|
|
|
|
\subsection{Schwanz ab!}
|
|
|
|
Diese Variante basiert darauf, daß die binäre Datei ohne weitere Codierung an
|
|
das Shell-Skript angehängt wurde. Nachteil dieses Verfahrens ist, daß das
|
|
`abschneidende Kommando' nach jeder Änderung der Länge des Skriptes angepaßt
|
|
werden muß.
|
|
|
|
Dabei gibt es zwei Methoden, die angehängte Datei wieder abzuschneiden. Die
|
|
einfachere Methode funktioniert mit \texttt{tail}:
|
|
|
|
\texttt{tail -n +227 \$0 > icon.png}
|
|
|
|
Dieses Beispiel geht davon aus, daß das Skript selbst 227 Zeilen umfaßt. Die
|
|
binäre Datei wurde mit einem Kommando wie \texttt{cat icon.png >> skript.sh} an
|
|
das Skript angehängt.
|
|
|
|
Für die etwas kompliziertere Variante muß die Länge des eigentlichen
|
|
Skript-Teiles genau angepaßt werden. Wenn das Skript beispielsweise etwa 5,5kB
|
|
lang ist, müssen genau passend viele Leerzeilen oder Kommentarzeichen angehängt
|
|
werden, damit sich eine Länge von 6kB ergibt. Dann kann das Anhängsel mit dem
|
|
Kommando \texttt{dd} in der folgenden Form abgeschnitten werden:
|
|
|
|
\texttt{dd bs=1024 if=\$0 of=icon.png skip=6}
|
|
|
|
Das Kommando kopiert Daten aus einer Eingabe- in eine Ausgabedatei. Im
|
|
einzelnen wird hier eine Blockgröße (blocksize, bs) von 1024 Bytes festgelegt.
|
|
Dann werden Eingabe- und Ausgabedatei benannt, dabei wird als Eingabedatei
|
|
\texttt{\$0} und somit der Name des laufenden Skriptes benutzt. Schließlich
|
|
wird festgelegt, daß bei der Aktion die ersten sechs Block~--~also die ersten
|
|
sechs Kilobytes~--~übersprungen werden sollen.
|
|
|
|
Um es nochmal zu betonen: Diese beiden Methoden sind mit Vorsicht zu genießen.
|
|
Bei der ersten führt jede zusätzliche oder gelöschte Zeile zu einer kaputten
|
|
Ausgabedatei, bei der zweiten reichen schon einzelne Zeilen. In jedem Fall
|
|
sollte nach dem Auspacken noch einmal mittels \texttt{sum} oder \texttt{md5sum}
|
|
eine Checksumme gezogen und verglichen werden.
|
|
|
|
\section{Dateien, die es nicht gibt}
|
|
|
|
TODO!!! Dateien, die es nicht gibt
|
|
|
|
\subsection{Speichern in nicht existente Dateien}
|
|
|
|
TODO!!! Speichern in nicht existente Dateien
|
|
|
|
\subsection{Subshell-Schleifen vermeiden}\label{subshellschleifen}
|
|
|
|
Wir wollen ein Skript schreiben, das die \texttt{/etc/passwd} liest und dabei
|
|
zählt, wie viele Benutzer eine UID kleiner als 100 haben.
|
|
|
|
Folgendes Skript funktioniert nicht:
|
|
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
count=0
|
|
cat /etc/passwd | while read i; do
|
|
uid=`echo $i | cut -f 3 -d:`
|
|
if [ $uid -lt 100 ]; then
|
|
count=`expr $count + 1`
|
|
echo $count
|
|
fi
|
|
done
|
|
echo Es sind $count Benutzer mit einer ID kleiner 100 eingetragen
|
|
\end{lstlisting}
|
|
|
|
Was ist passiert?
|
|
|
|
Dieses Skript besteht im Wesentlichen aus einer Pipe. Wir haben ein
|
|
\texttt{cat}-Kom\-man\-do, das den Inhalt der \texttt{/etc/passwd} durch eben
|
|
diese Pipe an eine Schleife übergibt. Das \texttt{read}-Kommando in der
|
|
Schleife liest die einzelnen Zeilen aus, dann folgt ein Bißchen Auswertung.
|
|
|
|
Es ist zu beobachten, daß bei der Ausgabe in Zeile 7 die Variable
|
|
\texttt{\$count} korrekte Werte enthält. Um so unverständlicher ist es, daß sie
|
|
nach der Vollendung der Schleife wieder den Wert 0 enthält.
|
|
|
|
Das liegt daran, daß diese Schleife als Teil einer Pipe in einer Subshell
|
|
ausgeführt wird. Die Variable \texttt{\$count} steht damit in der Schleife
|
|
praktisch nur lokal zur Verfügung, sie wird nicht an das umgebende Skript
|
|
`hochgereicht'.
|
|
|
|
Neben der Methode in \ref{daten_hochreichen} bietet sich hier eine viel
|
|
einfachere Lösung an:
|
|
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
count=0
|
|
while read i; do
|
|
uid=`echo $i | cut -f 3 -d:`
|
|
if [ $uid -lt 100 ]; then
|
|
count=`expr $count + 1`
|
|
echo $count
|
|
fi
|
|
done < /etc/passwd
|
|
echo Es sind $count Benutzer mit einer ID kleiner 100 eingetragen
|
|
\end{lstlisting}
|
|
|
|
Hier befindet sich die Schleife nicht in einer Pipe, daher wird sie auch nicht
|
|
in einer Subshell ausgeführt. Man kann auf das \texttt{cat}-Kommando verzichten
|
|
und den Inhalt der Datei durch die Umlenkung in Zeile 9 direkt auf die
|
|
Standardeingabe der Schleife (und somit auf das \texttt{read}-Kommando) legen.
|
|
|
|
\subsection{Daten aus einer Subshell hochreichen}\label{daten_hochreichen}
|
|
|
|
Ein immer wieder auftretendes und oft sehr verwirrendes Problem ist, daß
|
|
Variablen die in einer Subshell definiert wurden außerhalb dieser nicht
|
|
sichtbar sind (siehe Abschnitt \ref{subshellschleifen}). Dies ist um so
|
|
ärgerlicher, als daß Subshells auch bei vergleichsweise einfachen Pipelines
|
|
geöffnet werden.
|
|
|
|
Ein Beispiel für ein mißlingendes Skriptfragment wäre das folgende:
|
|
|
|
\begin{lstlisting}
|
|
nCounter=0
|
|
cat datei.txt | while read VAR; do
|
|
nCounter=`expr $nCounter + 1`
|
|
done
|
|
echo "nCounter=$nCounter"
|
|
\end{lstlisting}
|
|
|
|
Die Variable nCounter wird mit 0 initialisiert. Dann wird eine Datei per Pipe
|
|
in eine \texttt{while}-Schleife geleitet. Innerhalb der Schleife wird für jede
|
|
eingehende Zeile die Variable hochgezählt. Am Ende der Schleife enthält die
|
|
Variable tatsächlich den korrekten Wert, aber da die Pipe eine Subshell
|
|
geöffnet hat ist der Wert nach Beendigung der Schleife nicht mehr sichtbar. Das
|
|
\texttt{echo}-Kommando gibt die Zahl 0 aus.
|
|
|
|
Es gibt mehrere Ansätze, diesem Problem zu begegnen. Am einfachsten wäre es in
|
|
diesem Fall, dem Rat aus Abschnitt \ref{subshellschleifen} zu folgen und die
|
|
Subshell geschickt zu vermeiden. Doch das ist leider nicht immer möglich. Wie
|
|
geht man in solchen Fällen vor?
|
|
|
|
Bei einfachen Zahlenwerten könnte beispielsweise ein Rückgabewert helfen.
|
|
Komplexere Informationen können in eine temporäre Datei geschrieben werden, die
|
|
danach geparst werden müßte. Wenn die Informationen in Zeilen der Form
|
|
`VARIABLE=\dq{}Wert\dq{}' gespeichert werden, kann die Datei einfach mittels
|
|
\texttt{source} (Abschnitt \ref{source}) oder einem Konstrukt der Art
|
|
\texttt{eval `cat tempfile`} gelesen werden.
|
|
|
|
Und genau mit dieser Überlegung kommen wir zu einem eleganten~--~wenn auch
|
|
nicht ganz einfachen~--~Trick.
|
|
|
|
Anstatt die Daten in eine temporäre Datei zu schreiben, wo sie womöglich durch
|
|
andere Prozesse verändert oder ausgelesen werden könnten, kann man sie auch in
|
|
`nicht existente' Dateien schreiben. Das folgende Beispiel demonstriert das
|
|
Verfahren:
|
|
|
|
\begin{lstlisting}
|
|
#!/bin/sh -x
|
|
TMPNAME="/tmp/`date '+%Y%m%d%H%M%S'`$$.txt"
|
|
exec 3> "$TMPNAME"
|
|
exec 4< "$TMPNAME"
|
|
rm -f "$TMPNAME"
|
|
\end{lstlisting}
|
|
|
|
Bis hierher wurde zunächst eine temporäre Datei angelegt. Die Filehandles 3 und
|
|
4 wurden zum Schreiben bzw. Lesen mit dieser Datei verbunden. Daraufhin wurde
|
|
die Datei entfernt. Die Filehandles verweisen weiterhin auf die Datei, obwohl
|
|
sie im Dateisystem nicht mehr sichtbar ist.
|
|
|
|
Kommen wir zum nützlichen Teil des Skriptes:
|
|
|
|
\begin{lstlisting}[firstnumber=6]
|
|
nCounter=0
|
|
cat datei.txt | ( while read VAR; do
|
|
while read VAR; do
|
|
nCounter=`expr $nCounter + 1`
|
|
done
|
|
echo "nCounter=$nCounter"
|
|
) >&3
|
|
\end{lstlisting}
|
|
|
|
Hier wurde wieder die Variable nCounter initialisiert und in der Subshell die
|
|
Zeilen gezählt wie im ersten Beispiel. Allerdings wurde explizit eine Subshell
|
|
um die Schleife gestartet. Dadurch steht die in der Schleife hochgezählte
|
|
Variable auch nach Beendigung der Schleife zur Verfügung, allerdings immernoch
|
|
nur in der Subshell. Um das zu ändern, wird in Zeile 11 der Wert ausgegeben.
|
|
Die Ausgaben der Subshell werden in den oben erstellen Deskriptor umgeleitet.
|
|
|
|
\begin{lstlisting}[firstnumber=13]
|
|
echo "(vor eval) nCounter=$nCounter"
|
|
eval `cat <&4`
|
|
echo "(nach eval) nCounter=$nCounter"
|
|
\end{lstlisting}
|
|
|
|
Das \texttt{echo}-Kommando in Zeile 13 beweist, daß der Wert von nCounter
|
|
tatsächlich außerhalb der Subshell nicht zur Verfügung steht. Zunächst.
|
|
|
|
In Zeile 14 wird dann die ebenfalls oben schon angesprochene
|
|
\texttt{eval}-Zeile benutzt, um die Informationen aus dem Filedeskriptor zu
|
|
lesen, die die Schleife dort hinterlassen hat.
|
|
|
|
Abschließend zeigt die Zeile 15, daß der Transport tatsächlich funktioniert
|
|
hat, die Variable nCounter ist mit dem Wert aus der Subshell belegt.
|
|
|
|
|
|
\subsection{Dateien gleichzeitig lesen und schreiben}
|
|
|
|
Es kommt vor, daß man eine Datei bearbeiten möchte, die hinterher aber wieder
|
|
unter dem gleichen Namen zur Verfügung stehen soll. Beispielsweise sollen alle
|
|
Zeilen aus einer Datei entfernt werden, die nicht das Wort \textit{wichtig}
|
|
enthalten.
|
|
|
|
Der erste Versuch an der Stelle wird etwas in der Form
|
|
|
|
\lstinline|grep wichtig datei.txt > datei.txt|
|
|
|
|
sein. Das kann funktionieren, es kann aber auch in die sprichwörtliche Hose
|
|
gehen. Das Problem an der Stelle ist, daß die Datei an der Stelle gleichzeitig
|
|
zum Lesen und zum Schreiben geöffnet wird. Das Ergebnis ist undefiniert.
|
|
|
|
Eine Elegante Lösung besteht darin, einen Filedeskriptor auf die Quelldatei zu
|
|
legen und diese dann zu löschen. Die Datei wird erst dann wirklich aus dem
|
|
Dateisystem entfernt, wenn kein Deskriptor mehr auf sie zeigt. Dann kann aus
|
|
dem gerade angelegten Deskriptor gelesen werden, während eine neue Datei unter
|
|
dem alten Namen angelegt wird:
|
|
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
FILE=datei.txt
|
|
exec 3< "$FILE"
|
|
rm "$FILE"
|
|
grep "wichtig" <&3 > "$FILE"
|
|
\end{lstlisting}
|
|
|
|
Allerdings sollte man bei dieser Methode beachten, daß man im Falle eines
|
|
Fehlers die Quelldaten verliert, da die Datei ja bereits gelöscht wurde.
|
|
|
|
\section{Auf der Lauer: Wachhunde}\label{wachhunde}\index{Watchdog}
|
|
|
|
Es kommt vor, daß man einen Prozeß startet, bei dem man sich nicht sicher sein
|
|
kann daß er sich auch in absehbarer Zeit wieder beendet. Beispielsweise kann
|
|
der Timeout für einen Netzwerkzugriff deutlich höher liegen als erwünscht, und
|
|
wenn der `gegnerische' Dienst nicht antwortet bleibt einem nur zu warten.
|
|
|
|
Es sei denn, man legt einen geeigneten Wachhund\footnote{Der englische Begriff
|
|
`Watchdog' ist in diesem Zusammenhang wahrscheinlich geläufiger...} auf die
|
|
Lauer, der im Notfall rettend eingreift. In einem Shell-Skript könnte das wie
|
|
folgt aussehen:
|
|
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
timeout=5
|
|
ping 192.168.0.254 &
|
|
cmdpid=$!
|
|
\end{lstlisting}
|
|
|
|
Bis hierher nichts aufregendes. Eine Variable wird mit dem Timeout belegt, also
|
|
mit der Anzahl an Sekunden nach denen der zu überwachende Prozeß unterbrochen
|
|
werden soll. Dann wird der zu überwachende Prozeß gestartet und mittels \& in
|
|
den Hintergrund geschickt. Die Prozeß-ID des Prozesses wird in der Variablen
|
|
cmdpid gesichert.
|
|
|
|
\begin{lstlisting}[firstnumber=5]
|
|
(sleep $timeout; kill -9 $cmdpid) &
|
|
watchdogpid=$!
|
|
\end{lstlisting}
|
|
|
|
In Zeile 5 findet sich der eigentliche Watchdog. Hier wird eine Subshell
|
|
gestartet, in der zunächst der oben eingestellte Timeout abgewartet und dann
|
|
der zu überwachende Prozeß getötet wird. Diese Subshell wird ebenfalls mit \&
|
|
in den Hintergrund geschickt. Die ID der Subshell wird in der Variablen
|
|
watchdogpid gesichert.
|
|
|
|
\begin{lstlisting}[firstnumber=7]
|
|
wait $cmdpid
|
|
kill $watchdogpid > /dev/null 2>&1
|
|
exit 0
|
|
\end{lstlisting}
|
|
|
|
Dann wird durch ein \texttt{wait}\index{wait} darauf gewartet, daß sich der
|
|
überwachte Prozeß beendet. Dabei würde \texttt{wait} bis in alle Ewigkeit
|
|
warten, wäre da nicht der Watchdog in der Subshell. Wenn dem die Ausführung zu
|
|
lange dauert, sorgt er dafür daß der Prozeß beendet wird.
|
|
|
|
Kommt der überwachte Prozeß aber rechtzeitig zurück, sorgt \texttt{kill} in
|
|
Zeile 8 dafür daß der Wachhund `eingeschläfert' wird.
|
|
|
|
Auf diese Weise ist sichergestellt, daß der \texttt{ping} auf keinen Fall
|
|
länger als fünf Sekunden läuft.
|
|
|