Shell-Programmierung/beispiele.tex
2005-02-03 22:24:18 +00:00

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}