Shell-Programmierung/beispiele.tex

495 lines
22 KiB
TeX
Raw Normal View History

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}
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<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
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{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<65>rige Runlevel initialisiert wird.
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.
Das Ergebnis der Ausf<73>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.
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}
2001-07-02 12:52: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
(normalerweise \Ovalbox{CTRL}-\Ovalbox{C}) unterbrochen werden. Durch Druck auf
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
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<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}
Ein weiterer wertvoller Trick, diesmal im Zusammenhang mit \texttt{find} ist
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
Das f<>hrt wieder nicht zu dem gew<65>nschten Ergebnis. Da die Unterverzeichnisse
nicht die Extension `*.pl' tragen, werden sie nicht ber<65>cksichtigt. F<>r die
Suche in Unterverzeichnissen ziehen wir \texttt{find} (Siehe Abschnitt
\ref{find}) heran:
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}