2003-04-11 15:05:25 +00:00
% $Id$
2001-07-02 12:52:18 +00:00
\chapter { Beispiele}
\section { Schleifen und R<> ckgabewerte} \label { beisp_ schleifen_ exitcode} \index { Schleife} \index { R<EFBFBD> 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<6C> <6D> igen Abst<73> nden das Kommando \texttt { who} \index { who=\texttt { who} } ausf<73> 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 <20> berpr<70> ft, ob der Admin angemeldet ist. Wir erreichen das mit dem folgenden Code:
\index { \^ =\texttt { \^ } } \index { Anf<EFBFBD> hrungszeichen} \index { Pipe} \index { grep=\texttt { grep} } \index { sleep=\texttt { sleep} } \index { who=\texttt { who} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting}
2001-07-02 12:52:18 +00:00
#!/bin/sh
2005-01-21 17:23:30 +00:00
until who | grep "^ root "; do
sleep 30
2001-07-02 12:52:18 +00:00
done
2005-01-21 17:23:30 +00:00
echo "Big Brother is watching you!"
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
2004-12-10 14:38:03 +00:00
Das Skript f<> hrt also so lange das Kommando aus, bis die Ausf<73> 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<63> ftigt ist, wird im Schleifenk<6E> rper ein
\lstinline |sleep 30|\index { sleep=\texttt { sleep} } ausgef<65> hrt, um den Proze<7A> f<> r
30 Sekunden schlafen zu schicken. Sobald der Admin sich eingeloggt hat, wird
eine entsprechende Meldung ausgegeben.
2001-07-02 12:52:18 +00:00
\subsection { Schleife, bis ein Kommando erfolglos war}
2005-01-28 10:07:07 +00:00
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:
2001-07-02 12:52:18 +00:00
\index { \^ =\texttt { \^ } } \index { Anf<EFBFBD> hrungszeichen} \index { grep=\texttt { grep} } \index { Pipe} \index { sleep=\texttt { sleep} } \index { who=\texttt { who} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting}
2001-07-02 12:52:18 +00:00
#!/bin/sh
2005-01-21 17:23:30 +00:00
while who | grep "^ root "; do
sleep 30
2001-07-02 12:52:18 +00:00
done
2005-01-21 17:23:30 +00:00
echo "Die Katze ist aus dem Haus, Zeit, da<64> die M<> use tanzen!"
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
2005-01-28 10:07:07 +00:00
Die Schleife wird n<> mlich dann so lange ausgef<65> hrt, bis
\texttt { grep} \index { grep=\texttt { grep} } einen Fehler (bzw. eine erfolglose
Suche) zur<75> ckmeldet.
\section { Subshell-Schleifen vermeiden} \label { subshellschleifen}
Wir wollen ein Skript schreiben, das die \texttt { /etc/passwd} liest und dabei
z<EFBFBD> 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 <20> bergibt. Das \texttt { read} -Kommando in der
Schleife liest die einzelnen Zeilen aus, dann folgt ein Bi<42> chen Auswertung.
Es ist zu beobachten, da<64> bei der Ausgabe in Zeile 7 die Variable
\texttt { \$ count} korrekte Werte enth<74> lt. Um so unverst<73> ndlicher ist es, da<64> sie
nach der Vollendung der Schleife wieder den Wert 0 enth<74> lt.
Das liegt daran, da<64> diese Schleife als Teil einer Pipe in einer Subshell
ausgef<EFBFBD> hrt wird. Die Variable \texttt { \$ count} steht damit in der Schleife
praktisch nur lokal zur Verf<72> 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<65> 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.
2001-07-02 12:52:18 +00:00
\section { Ein typisches Init-Skript} \label { init-skript} \index { Init-Skript}
2005-02-03 22:24:18 +00:00
Dieses Skript dient dazu, den Apache HTTP-Server zu starten. Es wird w<> hrend
des Bootvorgangs gestartet, wenn der dazugeh<65> rige Runlevel initialisiert wird.
2001-07-02 12:52:18 +00:00
2005-02-03 22:24:18 +00:00
Das Skript mu<6D> 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} <20> bergeben wurden, wird
eine entsprechende Meldung angezeigt.
2001-07-02 12:52:18 +00:00
2005-02-03 22:24:18 +00:00
Das Ergebnis der Ausf<73> hrung wird mit Funktionen\index { Funktion} dargestellt,
die aus der Datei \lstinline |functions| stammen. Ebenfalls in dieser Datei sind
Funktionen, die einen Dienst starten oder stoppen.
2001-07-02 12:52:18 +00:00
2005-02-03 22:24:18 +00:00
Zun<EFBFBD> chst wird festgelegt, da<64> dieses Skript in der Bourne-Shell ausgef<65> hrt
werden soll (\ref { auswahl_ der_ shell} ).
2004-12-10 14:38:03 +00:00
\begin { lstlisting}
2001-07-02 12:52:18 +00:00
#!/bin/sh
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2005-02-03 22:24:18 +00:00
Dann folgen Kommentare\index { Kommentar} , die den Sinn des Skriptes erl<72> utern
(\ref { kommentare} ).
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
#
# 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
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Jetzt wird die Datei mit den Funktionen\index { Funktion} eingebunden (\ref { source} ).
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { source=\texttt { source} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
# Source function library.
. /etc/rc.d/init.d/functions
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Hier werden die Aufrufparameter ausgewertet (\ref { case} ).
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { \$ n=\texttt { \$ } $ n $ } \index { Anf<EFBFBD> hrungszeichen} \index { case=\texttt { case} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
# See how we were called.
case "$ 1 " in
start)
echo -n "Starting httpd: "
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Nachdem eine Meldung <20> ber den auszuf<75> hrenden Vorgang ausgegeben wurde, wird die Funktion \texttt { daemon} aus der Funktionsbibliothek ausgef<65> hrt. Diese Funktion startet das Programm, dessen Name hier als Parameter\index { Parameter} <20> bergeben wird. Dann gibt sie eine Meldung <20> ber den Erfolg aus.
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
daemon httpd
echo
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Jetzt wird ein Lock-File\footnote { Ein Lock-File signalisiert anderen Prozessen, da<64> ein bestimmter Proze<7A> bereits gestartet ist. So kann ein zweiter Aufruf verhindert werden.} angelegt.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { Anf<EFBFBD> hrungszeichen} \index { touch=\texttt { touch} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
touch /var/lock/subsys/httpd
;;
stop)
echo -n "Shutting down http: "
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Hier passiert im Prinzip das gleiche wie oben, nur da<64> mit der Funktion \texttt { killproc} der Daemon angehalten wird.
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
killproc httpd
echo
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Danach werden Lock-File und PID-File\footnote { In einem sogenannten PID-File hinterlegen einige Prozesse ihre Proze<7A> -ID, um anderen Programmen den Zugriff zu erleichtern (z. B. um den Proze<7A> anzuhalten etc).} gel<65> scht.
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
rm -f /var/lock/subsys/httpd
rm -f /var/run/httpd.pid
;;
status)
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Die Funktion \texttt { status} stellt fest, ob der entsprechende Daemon bereits l<> uft, und gibt das Ergebnis aus.
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
status httpd
;;
restart)
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
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.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { \$ n=\texttt { \$ } $ n $ } \index { Anf<EFBFBD> hrungszeichen}
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
$ 0 stop
$ 0 start
;;
reload)
echo -n "Reloading httpd: "
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Hier sendet die \texttt { killproc} -Funktion dem Daemon ein Signal\index { Signal} das ihm sagt, da<64> er seine Konfiguration neu einlesen soll.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { \$ n=\texttt { \$ } $ n $ } \index { Anf<EFBFBD> hrungszeichen}
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
killproc httpd -HUP
echo
;;
*)
echo "Usage: $ 0 { start|stop|restart|reload|status } "
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Bei aufruf mit einem beliebigen anderen Parameter\index { Parameter} wird eine Kurzhilfe ausgegeben. Dann wird daf<61> r gesorgt, da<64> das Skript mit dem Exit-Code 1 beendet wird. So kann festgestellt werden, ob das Skript ordnungsgem<65> <6D> beendet wurde (\ref { exit} ).
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { exit=\texttt { exit} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
exit 1
esac
exit 0
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
\section { Parameter<EFBFBD> bergabe in der Praxis} \label { beisp_ parameter} \index { Parameter}
Es kommt in der Praxis sehr oft vor, da<64> man ein Skript schreibt, dem derAnwender Parameter\index { Parameter} <20> 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<75> ck.
Das soll an folgendem Skript verdeutlicht werden. Das Skript kennt die Optionen \texttt { -a} und \texttt { -b} . Letzterer Option mu<6D> ein zus<75> tzlicher Wert mitgegeben werden. Alle anderen Parameter\index { Parameter} werden als Dateinamen interpretiert.
\index { \$ @=\texttt { \$ @} } \index { Anf<EFBFBD> hrungszeichen} \index { Backticks} \index { !|!|=\texttt { !|!|} } \index { getopt=\texttt { getopt} } \index { OR} \index { set=\texttt { set} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting}
2001-07-02 12:52:18 +00:00
#!/bin/sh
set -- `getopt "ab:" "$ @"` || {
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Das \texttt { set} \index { set=\texttt { set} } -Kommando belegt den Inhalt der vordefinierten Variablen (\ref { vordefinierte_ variablen} ) neu, so da<64> es aussieht, als ob dem Skript die R<> ckgabewerte von \texttt { getopt} \index { getopt=\texttt { getopt} } <20> bergeben wurden. Man mu<6D> die beiden Minuszeichen angeben, da sie daf<61> r sorgen, da<64> die Aufrufparameter an \texttt { getopt} \index { getopt=\texttt { getopt} } und nicht an die Shell selbst <20> bergeben werden. Die originalen Parameter\index { Parameter} werden von \texttt { getopt} \index { getopt=\texttt { getopt} } untersucht und modifiziert zur<75> ckgegeben: \texttt { a} und \texttt { b} werden als Parameter\index { Parameter} Markiert, \texttt { b} sogar mit der M<> glichkeit einer zus<75> tzlichen Angabe.
Wenn dieses Kommando fehlschl<68> gt ist das ein Zeichen daf<61> r, da<64> falsche Parameter\index { Parameter} <20> bergeben wurden. Also wird nach einer entsprechenden Meldung das Programm mit Exit-Code 1 verlassen.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { \$ n=\texttt { \$ } $ n $ } \index { Null-Befehl} \index { !>\& =\texttt { !>\& } } \index { !==\texttt { !=} } \index { Anf<EFBFBD> hrungszeichen} \index { Backticks} \index { basename=\texttt { basename} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
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
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
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.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { !==\texttt { !=} } \index { \$ n=\texttt { \$ } $ n $ } \index { Anf<EFBFBD> hrungszeichen} \index { case=\texttt { case} } \index { shift=\texttt { shift} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
case "$ 1 " in
-a) aflag=1 ;;
-b) shift; name="$ 1 " ;;
--) break ;;
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Wenn ein \texttt { -{ } -} erscheint, ist das ein Hinweis darauf, da<64> 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.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { shift=\texttt { shift} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
esac
shift
done
shift
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
Am Ende werden die Feststellungen ausgegeben.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { \$ * =\texttt { \$ * } } \index { Anf<EFBFBD> hrungszeichen}
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
echo "aflag=$ aflag / Name = $ name / Die Dateien sind $ * "
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2001-07-02 12:52:18 +00:00
2005-01-21 17:23:30 +00:00
\section { Fallensteller: Auf Traps reagieren} \label { traps} \index { trap=\texttt { trap} |(} \index { Signal|(}
2001-07-02 12:52:18 +00:00
2005-01-21 17:23:30 +00:00
Ein laufendes Shell-Skript kann durch Druck auf die Interrupt-Taste
2005-02-03 22:24:18 +00:00
(\Ovalbox { CTRL} +\Ovalbox { C} ) unterbrochen werden. Durch Druck auf
2005-01-21 17:23:30 +00:00
diese Taste wird ein Signal an den entsprechenden Proze<7A> gesandt, das ihn
bittet sich zu beenden. Dieses Signal hei<65> t SIGINT (f<> r SIGnal INTerrupt) und
tr<EFBFBD> gt die Nummer 2. Das kann ein kleines Problem darstellen, wenn das Skript
sich tempor<6F> re Dateien angelegt hat, da diese nach der Ausf<73> hrung nur noch
unn<EFBFBD> tig Platz verbrauchen und eigentlich gel<65> scht werden sollten. Man kann
sich sicher auch noch wichtigere F<> lle vorstellen, in denen ein Skript
bestimmte Aufgaben auf jeden Fall erledigen mu<6D> , bevor es sich beendet.
2001-07-02 12:52:18 +00:00
2005-01-21 17:23:30 +00:00
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
2001-07-02 12:52:18 +00:00
\LTXtable { \textwidth } { tab_ signale.tex}
2005-01-21 17:23:30 +00:00
Wie l<> st man jetzt dieses Problem? Gl<47> cklicherweise verf<72> gt die Shell <20> ber das
\texttt { trap} -Kommando, mit dessen Hilfe man auf diese Signale reagieren kann.
Die Anwendung soll in folgendem Skript beispielhaft dargestellt werden.
2001-07-02 12:52:18 +00:00
2005-01-21 17:23:30 +00:00
Das Skript soll eine komprimierte Textdatei mittels \texttt { zcat} in ein
tempor<EFBFBD> res File entpacken, dieses mit \texttt { pg} seitenweise anzeigen und
nachher wieder l<> schen.
2001-07-02 12:52:18 +00:00
\index { !==\texttt { !=} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting}
2001-07-02 12:52:18 +00:00
#!/bin/sh
stat=1
temp=/tmp/zeige$$
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2005-01-21 17:23:30 +00:00
Zun<EFBFBD> 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<75> ckliefern soll. Die Variable \texttt { temp} enth<74> lt
den Namen f<> r eine tempor<6F> re Datei. Dieser setzt sich zusammen aus
\texttt { /tmp/zeige} und der Proze<7A> nummer des laufenden Skripts. So soll
sichergestellt werden, da<64> noch keine Datei mit diesem Namen existiert.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { Ticks} \index { !>\& =\texttt { !>\& } } \index { \$ n=\texttt { \$ } $ n $ } \index { Ticks} \index { Anf<EFBFBD> hrungszeichen} \index { Backticks} \index { basename=\texttt { basename} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
trap 'rm -f $ temp; exit $ stat' 0
trap 'echo "`basename $ 0 `: Ooops..." 1 > & 2 ' 1 2 15
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2005-01-21 17:23:30 +00:00
Hier werden die Traps definiert. Bei Signal 0 wird die tempor<6F> re Datei gel<65> scht
und der Wert aus der Variable \texttt { stat} als Exit-Code zur<75> 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<65> t, er wird bei jedem unnormalen
Ende ausgef<65> hrt. Er gibt eine entsprechende Meldung auf die
Standard-Fehlerausgabe (\ref { datenstrom} ) aus. Danach wird das Skript beendet,
und der erste Trap wird ausgef<65> hrt.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { \$ \# =\texttt { \$ \# } } \index { !==\texttt { !=} } \index { !>=\texttt { !>} } \index { \$ n=\texttt { \$ } $ n $ } \index { Anf<EFBFBD> hrungszeichen} \index { case=\texttt { case} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
case $ # in
1) zcat "$ 1 " > $ temp
pg $ temp
stat=0
;;
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2005-01-21 17:23:30 +00:00
Jetzt kommt die eigentliche Funktionalit<69> t des Skriptes: Das
\texttt { case} -Kommando (\ref { case} ) testet die Anzahl der <20> bergebenen
Parameter\index { Parameter} . Wenn genau ein Parameter\index { Parameter} <20> bergeben
wurde, entpackt \texttt { zcat} die Datei, die im ersten
Parameter\index { Parameter} angegeben wurde, in die tempor<6F> 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<75> ckgegeben wird.
2004-12-10 14:38:03 +00:00
2001-07-02 12:52:18 +00:00
\index { !>\& =\texttt { !>\& } } \index { \$ n=\texttt { \$ } $ n $ } \index { Anf<EFBFBD> hrungszeichen} \index { Backticks} \index { basename=\texttt { basename} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2001-07-02 12:52:18 +00:00
*) echo "Anwendung: `basename $ 0 ` Dateiname" 1 > & 2
esac
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2005-01-21 17:23:30 +00:00
Wenn \texttt { case} eine andere Parameterzahl feststellt, wird eine Meldung mit
der Aufrufsyntax auf die Standard-Fehlerausgabe geschrieben.
2001-07-02 12:52:18 +00:00
\index { trap=\texttt { trap} |)} \index { Signal|)}
2002-04-12 11:55:28 +00:00
2004-11-05 16:20:53 +00:00
\section { Chaoten: Dateien in zuf<75> llige Reihenfolge bringen} \label { chaoten} \index { Zufallszahlen|(}
2002-04-12 11:55:28 +00:00
Wir wollen uns einen MP3-Player programmieren, der Alle MP3-Dateien aus einem
bestimmten Verzeichnisbaum in zuf<75> lliger Reihenfolge abspielt. Damit dieses
Problem f<> r uns eine Herausforderung darstellt\footnote { Denn schlie<69> lich hat
2005-01-28 10:07:07 +00:00
mpg123 schon von Hause aus eine Random-Funktion.} , wollen wir vor dem Abspielen
2002-04-12 11:55:28 +00:00
der jeweiligen Datei etwas mit dem Dateinamen anstellen. Ob das eine einfache
Ausgabe per \texttt { echo} ist, oder ob der Name per Sprachsynthese oder auf
einem externen Display angezeigt werden soll ist an dieser Stelle egal.
Das Problem ist, da<64> wir in der Shell nur <20> ber Umwege an Zufallszahlen kommen
k<EFBFBD> nnen. Auf Systemen, in denen die Datei \texttt { /dev/urandom} existiert,
liefert uns der Kernel aber schon sehr zuf<75> llige Zeichenfolgen. Diese Folgen
k<EFBFBD> nnen alle Zeichen enthalten, daher m<> ssen sie vor der Benutzung f<> r unsere
2004-11-05 16:20:53 +00:00
Zwecke noch etwas `bereinigt' werden.
2002-04-12 11:55:28 +00:00
Wie das aussieht, wenn es fertig ist, sieht man im folgenden Skript:
\index { !==\texttt { !=} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting}
2002-04-12 11:55:28 +00:00
#!/bin/sh
for i in `find $ 1 - type f - name " * . [ mM ] [ pP ] 3 "`; do
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2002-04-12 11:55:28 +00:00
Hier beginnt eine Schleife, die <20> ber alle Ausgaben des \texttt { find} -Kommandos
iteriert. Dabei sucht \texttt { find} nach allen normalen Dateien (\texttt { -type
f} ), die die Extension .mp3 tragen (\texttt { -name \dq * .[mM][pP]3\dq } -- wir
ignorieren Gro<72> - / Kleinschreibung).
2004-12-10 14:38:03 +00:00
2002-04-12 11:55:28 +00:00
\index { find=\texttt { find} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2002-04-12 11:55:28 +00:00
echo `tr -dc "[:alpha:]" < /dev/urandom | \
dd count=8 bs=1 2> /dev/null`$ i
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2004-11-05 16:20:53 +00:00
Hier ist der `magische Teil'. Mit dem \texttt { echo} wird die Ausgabe einer Pipe
2002-04-12 11:55:28 +00:00
ausgegeben, gefolgt von dem aktuellen Dateinamen. Diese Pipe enth<74> lt ein
\texttt { tr} , der alle ungewollten Zeichen (alles, was kein Textzeichen ist) aus
einem Datenstrom entfernt. Die Daten erh<72> lt \texttt { tr} durch die
\texttt { <} -Umleitung aus oben genannter Datei.
2004-11-05 16:20:53 +00:00
Diese Datei liefert `ohne Ende' Zeichen. Wir wollen aber nur acht Zeichen
2002-04-12 11:55:28 +00:00
haben, die wir unserem Dateinamen voranstellen k<> nnen. Dazu benutzen wir das
Kommando \texttt { dd} mit den angegebenen Parametern. Damit die Erfolgsmeldung
von \texttt { dd} nicht die Ausgabe verunstaltet, lenken wir sie nach
\texttt { /dev/null} um.
\index { tr=\texttt { tr} } \index { dd=\texttt { dd} }
2004-12-10 14:38:03 +00:00
2002-04-12 11:55:28 +00:00
\index { find=\texttt { find} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2002-04-12 11:55:28 +00:00
done | sort | cut -b 9- | while read i; do
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2002-04-12 11:55:28 +00:00
Das Ergebnis der obigen Schleife ist also die Liste der Dateinamen, denen
jeweils acht zuf<75> llige Zeichen vorangestellt wurden. Die Reihenfolge entspricht
allerdings immer noch der Ausgabe von \texttt { find} , wird also nach jedem
Durchlauf gleich sein.
Um das zu <20> ndern, pipen wir die Ausgabe der Schleife durch ein \texttt { sort} .
Da die ersten acht Zeichen jeder Zeile zuf<75> llig sind, erhalten wir so eine
zuf<EFBFBD> llige Reihenfolge der Zeilen. Jetzt m<> ssen wir nur noch durch ein
\texttt { cut} die zuf<75> lligen Zeichen abschneiden, und erhalten so die
urspr<EFBFBD> ngliche Liste von Dateien in einer zuf<75> lligen Reihenfolge.
Diese lesen wir jetzt zeilenweise mittels \texttt { read} ein. In der
\texttt { while} -Schleife k<> nnen wir alle erforderlichen Sachen mit dem
Dateinamen anstellen. Hier wird er nur mittels \texttt { echo} ausgegeben.
\index { sort=\texttt { sort} } \index { cut=\texttt { cut} } \index { read=\texttt { read} } \index { while=\texttt { while} }
2004-12-10 14:38:03 +00:00
2002-04-12 11:55:28 +00:00
\index { find=\texttt { find} }
2004-12-10 14:38:03 +00:00
\begin { lstlisting} [firstnumber=last]
2002-04-12 11:55:28 +00:00
echo "Jetzt wird $ i gespielt"
mpg123 "$ i"
done
2004-12-10 14:38:03 +00:00
\end { lstlisting}
2002-04-12 11:55:28 +00:00
\index { Zufallszahlen|)}
2004-11-05 16:20:53 +00:00
\section { Wer suchet, der findet} \label { beispiele_ suchen} \index { grep=\texttt { grep} |(textbf}
\subsection { Prozesse suchen} \label { beispiele_ suchen_ prozesse} \index { ps=\texttt { ps} |(textbf} \index { pgrep=\texttt { pgrep} |(textbf}
Im Zusammenhang mit grep st<73> <74> t fast jeder Shell-Skripter fr<66> her oder sp<73> ter auf
das Problem, da<64> er irgendwas davon abh<62> ngig machen will, ob ein bestimmter
Proze<EFBFBD> l<> uft oder nicht. Im Normalfall wird er zuerst folgendes ausprobieren,
was aber oft (nicht immer) in die Hose gehen wird:
2004-12-10 14:38:03 +00:00
\lstinline /ps aux | grep prozessname & & echo "l<> uft schon"/
2004-11-05 16:20:53 +00:00
Der Grund daf<61> r ist, da<64> unter Umst<73> nden in der Ausgabe von \texttt { ps} auch
das \texttt { grep} -Kommando samt Parameter (\textit { prozessname} ) aufgelistet
2004-11-19 12:09:34 +00:00
wird. So findet das \texttt { grep} -Kom\- man\- do sich quasi selbst.
2004-11-05 16:20:53 +00:00
Abhilfe schafft entweder \texttt { pgrep} (\ref { pgrep} ) oder das folgende
Konstrukt:
2004-12-10 14:38:03 +00:00
\lstinline /ps aux | grep "[p]rozessname" & & echo "l<> uft schon"/
2004-11-05 16:20:53 +00:00
Das p ist jetzt als eine Zeichenmenge (regul<75> rer Ausdruck) angegeben worden.
Jetzt sucht \texttt { grep} also nach dem String \textit { prozessname} , in der
Ausgabe von \texttt { ps} erscheint das \texttt { grep} -Kommando allerdings mit
\textit { [p]rozessname} und wird somit ignoriert.
\index { ps=\texttt { ps} |)textbf} \index { pgrep=\texttt { pgrep} |)textbf}
\subsection { Dateiinhalte suchen} \label { beispiele_ suchen_ dateien} \index { find=\texttt { find} |(textbf}
2005-01-28 10:07:07 +00:00
Ein weiterer wertvoller Trick, diesmal im Zusammenhang mit \texttt { find} , ist
2004-11-05 16:20:53 +00:00
folgendes Szenario: Es gibt ein Verzeichnis mit vielen Unterverzeichnissen,
<EFBFBD> berall liegen Perl-Skripte und andere Dateien. Gesucht sind alle Dateien, in
denen eine Zeile mit dem Inhalt `strict' vorkommt. Man k<> nnte jetzt
folgendes versuchen:
2004-12-10 14:38:03 +00:00
\lstinline |grep -r strict *|
2004-11-05 16:20:53 +00:00
Das f<> hrt allerdings dazu, da<64> alle Dateien durchsucht werden, nicht nur die
Perl-Skripte. Diese tragen nach unserer Konvention\footnote { Perl-Skripte m<> ssen
keine spezielle Extension haben, es sei aber um des Beispiels Willen mal
angenommen.} die Extension `.pl'. Wir starten also eine rekursive Suche <20> ber
alle Dateien, die dem Muster entsprechen:
2004-12-10 14:38:03 +00:00
\lstinline |grep -r strict *.pl|
2004-11-05 16:20:53 +00:00
2005-01-28 10:07:07 +00:00
Und wieder f<> hrt es nicht zu dem gew<65> nschten Ergebnis. Da die
Unterverzeichnisse nicht die Extension `*.pl' tragen, werden sie nicht
ber<EFBFBD> cksichtigt. F<> r die Suche in Unterverzeichnissen ziehen wir \texttt { find}
(Siehe Abschnitt \ref { find} ) heran:
2004-11-05 16:20:53 +00:00
2004-12-10 14:38:03 +00:00
\lstinline |find . -name \* .pl -exec grep strict { } \; |
2004-11-05 16:20:53 +00:00
Dieser Befehl gibt uns zwar die gefundenen Zeilen aus, nicht aber die Namen der
Dateien. Es sieht f<> r \texttt { grep} so aus als ob nur eine Datei durchsucht
w<EFBFBD> rde, da besteht keine Notwendigkeit den Namen anzugeben. Das ginge mit dem
Parameter \texttt { -l} , allerdings w<> rden uns dann nur noch die Dateinamen
angezeigt. Eine Ausgabe mit beiden Informationen erhalten wir mit dem folgenden
Konstrukt:
2004-12-10 14:38:03 +00:00
\lstinline |find . -name \* .pl -exec grep strict /dev/null { } \; |
2004-11-05 16:20:53 +00:00
Hier durchsucht \texttt { grep} nicht nur die gefundenen Dateien, sondern bei
jedem Aufruf auch \texttt { /dev/null} , also den digitalen M<> lleimer der per
Definition leer ist. Da es f<> r \texttt { grep} so aussieht als ob mehr als eine
Datei durchsucht w<> rden, wird bei jeder Fundstelle sowohl der Dateiname als
auch die gefundene Zeile ausgegeben.
\index { find=\texttt { find} |)textbf}
\index { grep=\texttt { grep} |)textbf}