565 lines
24 KiB
TeX
565 lines
24 KiB
TeX
% $Id$
|
|
\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:
|
|
|
|
\index{\^=\texttt{\^}}\index{Anführungszeichen}\index{Pipe}\index{grep=\texttt{grep}}\index{sleep=\texttt{sleep}}\index{who=\texttt{who}}
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
until who | grep "^root "; do
|
|
sleep 30
|
|
done
|
|
echo "Big Brother is watching you!"
|
|
\end{lstlisting}
|
|
|
|
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
|
|
\lstinline|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:
|
|
|
|
\index{\^=\texttt{\^}}\index{Anführungszeichen}\index{grep=\texttt{grep}}\index{Pipe}\index{sleep=\texttt{sleep}}\index{who=\texttt{who}}
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
while who | grep "^root "; do
|
|
sleep 30
|
|
done
|
|
echo "Die Katze ist aus dem Haus, Zeit, daß die Mäuse tanzen!"
|
|
\end{lstlisting}
|
|
|
|
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{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.
|
|
|
|
|
|
\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 \lstinline|functions| stammen. Ebenfalls in dieser Datei sind
|
|
Funktionen, die einen Dienst starten oder stoppen.
|
|
|
|
Zunächst wird festgelegt, daß dieses Skript in der Bourne-Shell ausgeführt
|
|
werden soll (\ref{auswahl_der_shell}).
|
|
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
|
|
\end{lstlisting}
|
|
|
|
Dann folgen Kommentare\index{Kommentar}, die den Sinn des Skriptes erläutern
|
|
(\ref{kommentare}).
|
|
|
|
\begin{lstlisting}[firstnumber=last]
|
|
#
|
|
# 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{lstlisting}
|
|
|
|
Jetzt wird die Datei mit den Funktionen\index{Funktion} eingebunden (\ref{source}).
|
|
|
|
\index{source=\texttt{source}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
# Source function library.
|
|
. /etc/rc.d/init.d/functions
|
|
|
|
\end{lstlisting}
|
|
|
|
Hier werden die Aufrufparameter ausgewertet (\ref{case}).
|
|
|
|
\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}\index{case=\texttt{case}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
# See how we were called.
|
|
case "$1" in
|
|
start)
|
|
echo -n "Starting httpd: "
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\begin{lstlisting}[firstnumber=last]
|
|
daemon httpd
|
|
echo
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\index{Anführungszeichen}\index{touch=\texttt{touch}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
touch /var/lock/subsys/httpd
|
|
;;
|
|
stop)
|
|
echo -n "Shutting down http: "
|
|
|
|
\end{lstlisting}
|
|
|
|
Hier passiert im Prinzip das gleiche wie oben, nur daß mit der Funktion \texttt{killproc} der Daemon angehalten wird.
|
|
|
|
\begin{lstlisting}[firstnumber=last]
|
|
killproc httpd
|
|
echo
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\begin{lstlisting}[firstnumber=last]
|
|
rm -f /var/lock/subsys/httpd
|
|
rm -f /var/run/httpd.pid
|
|
;;
|
|
status)
|
|
|
|
\end{lstlisting}
|
|
|
|
Die Funktion \texttt{status} stellt fest, ob der entsprechende Daemon bereits läuft, und gibt das Ergebnis aus.
|
|
|
|
\begin{lstlisting}[firstnumber=last]
|
|
status httpd
|
|
;;
|
|
restart)
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
$0 stop
|
|
$0 start
|
|
;;
|
|
reload)
|
|
echo -n "Reloading httpd: "
|
|
|
|
\end{lstlisting}
|
|
|
|
Hier sendet die \texttt{killproc}-Funktion dem Daemon ein Signal\index{Signal} das ihm sagt, daß er seine Konfiguration neu einlesen soll.
|
|
|
|
\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
killproc httpd -HUP
|
|
echo
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {start|stop|restart|reload|status}"
|
|
|
|
\end{lstlisting}
|
|
|
|
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}).
|
|
|
|
\index{exit=\texttt{exit}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
exit 1
|
|
esac
|
|
|
|
exit 0
|
|
|
|
\end{lstlisting}
|
|
|
|
|
|
\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.
|
|
|
|
\index{\$@=\texttt{\$@}}\index{Anführungszeichen}\index{Backticks}\index{!|!|=\texttt{!|!|}}\index{getopt=\texttt{getopt}}\index{OR}\index{set=\texttt{set}}
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
set -- `getopt "ab:" "$@"` || {
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\index{\$n=\texttt{\$}$n$}\index{Null-Befehl}\index{!>\&=\texttt{!>\&}}\index{!==\texttt{!=}}\index{Anführungszeichen}\index{Backticks}\index{basename=\texttt{basename}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
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{lstlisting}
|
|
|
|
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.
|
|
|
|
\index{!==\texttt{!=}}\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}\index{case=\texttt{case}}\index{shift=\texttt{shift}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
case "$1" in
|
|
-a) aflag=1 ;;
|
|
-b) shift; name="$1" ;;
|
|
--) break ;;
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\index{shift=\texttt{shift}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
esac
|
|
shift
|
|
done
|
|
shift
|
|
|
|
\end{lstlisting}
|
|
|
|
Am Ende werden die Feststellungen ausgegeben.
|
|
|
|
\index{\$*=\texttt{\$*}}\index{Anführungszeichen}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
echo "aflag=$aflag / Name = $name / Die Dateien sind $*"
|
|
|
|
\end{lstlisting}
|
|
|
|
|
|
\section{Fallensteller: Auf Traps reagieren}\label{traps}\index{trap=\texttt{trap}|(}\index{Signal|(}
|
|
|
|
Ein laufendes Shell-Skript kann durch Druck auf die Interrupt-Taste
|
|
(\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.
|
|
|
|
\index{!==\texttt{!=}}
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
stat=1
|
|
temp=/tmp/zeige$$
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\index{Ticks}\index{!>\&=\texttt{!>\&}}\index{\$n=\texttt{\$}$n$}\index{Ticks}\index{Anführungszeichen}\index{Backticks}\index{basename=\texttt{basename}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
trap 'rm -f $temp; exit $stat' 0
|
|
trap 'echo "`basename $0`: Ooops..." 1>&2' 1 2 15
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\index{\$\#=\texttt{\$\#}}\index{!==\texttt{!=}}\index{!>=\texttt{!>}}\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}\index{case=\texttt{case}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
case $# in
|
|
1) zcat "$1" > $temp
|
|
pg $temp
|
|
stat=0
|
|
;;
|
|
|
|
\end{lstlisting}
|
|
|
|
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.
|
|
|
|
\index{!>\&=\texttt{!>\&}}\index{\$n=\texttt{\$}$n$}\index{Anführungszeichen}\index{Backticks}\index{basename=\texttt{basename}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
*) echo "Anwendung: `basename $0` Dateiname" 1>&2
|
|
esac
|
|
|
|
\end{lstlisting}
|
|
|
|
Wenn \texttt{case} eine andere Parameterzahl feststellt, wird eine Meldung mit
|
|
der Aufrufsyntax auf die Standard-Fehlerausgabe geschrieben.
|
|
|
|
\index{trap=\texttt{trap}|)}\index{Signal|)}
|
|
|
|
|
|
\section{Chaoten: Dateien in zufällige Reihenfolge bringen}\label{chaoten}\index{Zufallszahlen|(}
|
|
|
|
Wir wollen uns einen MP3-Player programmieren, der Alle MP3-Dateien aus einem
|
|
bestimmten Verzeichnisbaum in zufälliger Reihenfolge abspielt. Damit dieses
|
|
Problem für uns eine Herausforderung darstellt\footnote{Denn schließlich hat
|
|
mpg123 schon von Hause aus eine Random-Funktion.}, wollen wir vor dem Abspielen
|
|
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ß wir in der Shell nur über Umwege an Zufallszahlen kommen
|
|
können. Auf Systemen, in denen die Datei \texttt{/dev/urandom} existiert,
|
|
liefert uns der Kernel aber schon sehr zufällige Zeichenfolgen. Diese Folgen
|
|
können alle Zeichen enthalten, daher müssen sie vor der Benutzung für unsere
|
|
Zwecke noch etwas `bereinigt' werden.
|
|
|
|
Wie das aussieht, wenn es fertig ist, sieht man im folgenden Skript:
|
|
|
|
\index{!==\texttt{!=}}
|
|
\begin{lstlisting}
|
|
#!/bin/sh
|
|
for i in `find $1 -type f -name "*.[mM][pP]3"`; do
|
|
|
|
\end{lstlisting}
|
|
|
|
Hier beginnt eine Schleife, die ü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ß- / Kleinschreibung).
|
|
|
|
\index{find=\texttt{find}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
echo `tr -dc "[:alpha:]" < /dev/urandom | \
|
|
dd count=8 bs=1 2> /dev/null`$i
|
|
|
|
\end{lstlisting}
|
|
|
|
Hier ist der `magische Teil'. Mit dem \texttt{echo} wird die Ausgabe einer Pipe
|
|
ausgegeben, gefolgt von dem aktuellen Dateinamen. Diese Pipe enthält ein
|
|
\texttt{tr}, der alle ungewollten Zeichen (alles, was kein Textzeichen ist) aus
|
|
einem Datenstrom entfernt. Die Daten erhält \texttt{tr} durch die
|
|
\texttt{<}-Umleitung aus oben genannter Datei.
|
|
|
|
Diese Datei liefert `ohne Ende' Zeichen. Wir wollen aber nur acht Zeichen
|
|
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}}
|
|
|
|
\index{find=\texttt{find}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
done | sort | cut -b 9- | while read i; do
|
|
|
|
\end{lstlisting}
|
|
|
|
Das Ergebnis der obigen Schleife ist also die Liste der Dateinamen, denen
|
|
jeweils acht zufä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 ändern, pipen wir die Ausgabe der Schleife durch ein \texttt{sort}.
|
|
Da die ersten acht Zeichen jeder Zeile zufällig sind, erhalten wir so eine
|
|
zufällige Reihenfolge der Zeilen. Jetzt müssen wir nur noch durch ein
|
|
\texttt{cut} die zufälligen Zeichen abschneiden, und erhalten so die
|
|
ursprüngliche Liste von Dateien in einer zufä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}}
|
|
|
|
\index{find=\texttt{find}}
|
|
\begin{lstlisting}[firstnumber=last]
|
|
echo "Jetzt wird $i gespielt"
|
|
mpg123 "$i"
|
|
done
|
|
|
|
\end{lstlisting}
|
|
|
|
\index{Zufallszahlen|)}
|
|
|
|
|
|
|
|
\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ößt fast jeder Shell-Skripter früher oder später auf
|
|
das Problem, daß er irgendwas davon abhängig machen will, ob ein bestimmter
|
|
Prozeß läuft oder nicht. Im Normalfall wird er zuerst folgendes ausprobieren,
|
|
was aber oft (nicht immer) in die Hose gehen wird:
|
|
|
|
\lstinline/ps aux | grep prozessname && echo "läuft schon"/
|
|
|
|
Der Grund dafür ist, daß unter Umständen in der Ausgabe von \texttt{ps} auch
|
|
das \texttt{grep}-Kommando samt Parameter (\textit{prozessname}) aufgelistet
|
|
wird. So findet das \texttt{grep}-Kom\-man\-do sich quasi selbst.
|
|
|
|
Abhilfe schafft entweder \texttt{pgrep} (\ref{pgrep}) oder das folgende
|
|
Konstrukt:
|
|
|
|
\lstinline/ps aux | grep "[p]rozessname" && echo "läuft schon"/
|
|
|
|
Das p ist jetzt als eine Zeichenmenge (regulä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}
|
|
|
|
Ein weiterer wertvoller Trick, diesmal im Zusammenhang mit \texttt{find}, ist
|
|
folgendes Szenario: Es gibt ein Verzeichnis mit vielen Unterverzeichnissen,
|
|
ü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:
|
|
|
|
\lstinline|grep -r strict *|
|
|
|
|
Das führt allerdings dazu, daß 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 über
|
|
alle Dateien, die dem Muster entsprechen:
|
|
|
|
\lstinline|grep -r strict *.pl|
|
|
|
|
Und wieder führt es nicht zu dem gewünschten Ergebnis. Da die
|
|
Unterverzeichnisse nicht die Extension `*.pl' tragen, werden sie nicht
|
|
berücksichtigt. Für die Suche in Unterverzeichnissen ziehen wir \texttt{find}
|
|
(Siehe Abschnitt \ref{find}) heran:
|
|
|
|
\lstinline|find . -name \*.pl -exec grep strict {} \;|
|
|
|
|
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ü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:
|
|
|
|
\lstinline|find . -name \*.pl -exec grep strict /dev/null {} \;|
|
|
|
|
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}
|