Shell-Programmierung/beispiele.tex
2005-01-06 10:48:37 +00:00

458 lines
22 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{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 \texttt{/etc/rc.d/init.d/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 (normalerweise \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|
Das führt wieder 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}