% $Id$ \chapter{Nützliche Shell-Kommandos}\label{nuetzliche_shell-kommandos} Durch die gezeigten Steuerungsmöglichkeiten stehen dem Shell-Pro\-grammie\-rer Möglichkeiten offen, fast alle gängigen Algorithmen zu implementieren. Es ist tatsächlich in der Shell möglich, Sortier- oder Suchfunktionen zu schreiben. Leider kommt aber an dieser Stelle einer der bedeutendsten Nachteile der Shell zum tragen: Die Geschwindigkeit. In einem Shell-Skript wird für jedes externe Kommando\footnote{Externe Kommandos sind solche, die nicht direkt in der Shell enthalten sind, für die also eine eigene Datei aufgerufen wird.} ein eigener Prozeß gestartet. Das kostet natürlich Zeit und Speicher. Zeitkritische Anwendungen sind also kein Einsatzgebiet für Shell-Skripte. Die schreibt man besser in Perl, oder noch besser in einer `compilierten' Sprache wie C oder C++. Es stehen jedoch an der Shell viele sehr nützliche externe Kommandos zur Verfügung, die einem die Entwicklung entsprechender eigener Routinen ersparen. Diese externen Kommandos sind zudem in anderen Sprachen geschrieben worden, so daß sie schneller ablaufen als jedes Shell-Skript. Man kommt als Shell-Programmierer nicht sinnvoll um den Einsatz dieser Programme herum. In diesem Abschnitt sollen einige dieser Programme mit typischen Einsatzmöglichkeiten vorgestellt werden. Eine vollständige Beschreibung wäre (wenn überhaupt möglich) viel zu lang, um an dieser Stelle untergebracht zu werden. Für ausführlichere Beschreibungen empfiehlt sich das Studium der Man-Pages oder der Kauf eines entsprechenden Buches. Am besten macht man natürlich beides. ;-) Eine globale Beschreibung aller gängigen Kommandos würde den Rahmen dieses Textes sprengen. Außerdem wäre es nicht leicht, das zu einer Aufgabe passende Werkzeug zu finden. Die Werkzeuge nach Aufgaben zu sortieren fällt allerdings auch nicht leicht. Die Entwickler der Kommandos versuchen, ihre Tools möglichst universell einsetzbar zu halten, also gibt es keine 1:1-Beziehung zwischen Problem und Lösung. Um sowohl das Finden eines Werkzeugs zu einem gegebenen Problem als auch das Finden einer Beschreibung zu einem gegebenen Werkzeug zu vereinfachen, und um die oben beschriebene n:m-Beziehung abzubilden, werden hier also zunächst typische Aufgaben beschrieben. Diese enthalten 'Links' zu den in Frage kommenden Werkzeugen. Danach gibt es eine alphabetische Aufzählung der wichtigsten Kommandos. \section{Nägel...}\label{naegel} \subsection{Ein- und Ausgabe}\label{ein_und_ausgabe} Beinahe jedes Skript verwendet in irgendeiner Form die Ein- oder Ausgabe. Sei es in interaktiver Art auf dem Terminal, oder im Hintergrund auf Dateien. Einige grundlegende Kommandos in diesem Zusammenhang werden hier vorgestellt. cat echo head printf read tail \subsection{Pfade und Dateien}\label{pfade_und_dateien} Eine der Hautaufgaben von Shell-Skripten ist natürlich das Hantieren mit Dateien. In diesem Abschnitt geht es allerdings nicht um den Umgang mit Dateiinhalten, sondern vielmehr werden einige nützliche Tools im Umgang mit Dateien an sich vorgestellt. basename dirname touch \subsection{Pipes manipulieren}\label{pipes_manipulieren}\index{Pipe|(textbf} Das Konzept der Pipes (Röhren) wird bereits in dem Kapitel über Befehlsformen (\ref{befehlsformen}) vorgestellt. Im wesentlichen besteht es darin, daß Daten von einem Programm an ein anderes weitergeleitet werden. Auf diese Weise entsteht eine sogenannte \textit{Pipeline}\index{Pipeline} aus mehreren Kommandos. Einige Kommandos sind für den Einsatz in einem solchen Konstrukt prädestiniert, obwohl die meisten auch alleine eingesetzt werden können. Einige dieser Tools sollen im Folgenden vorgestellt werden. grep sed awk sort tee wc \index{Pipe|)} \subsection{Prozeßmanagement}\label{prozessmanagement} TODO!! ps pgrep pkill kill \section{... und Hämmer}\label{haemmer} \subsection{awk} TODO!! \subsection{basename}\label{basename}\index{basename=\texttt{basename}|(textbf} Dem Tool \texttt{basename} wird als Parameter ein Pfad zu einer Datei übergeben. Der in der Angabe enthaltene Pfad wird abgeschnitten, nur der Name der eigentlichen Datei wird zurückgegeben. \index{basename=\texttt{basename}|)} \subsection{bc} TODO!!! \subsection{cat}\label{cat}\index{cat=\texttt{cat}|(textbf} Auch \texttt{cat} ist ein oft unterbewertetes Tool. Seine Aufgabe besteht zwar lediglich darin, etwas von der Standardeingabe oder aus einer Datei zu lesen, und das dann auf der Standardausgabe wieder auszugeben. Allerdings leistet es an vielen, teilweise sehr unterschiedlich gelagerten Aufgaben wertvolle Dienste. Durch Umlenklung der Ausgabe können Dateien erzeugt und erweitert werden. So können mehrere Dateien per \texttt{cat datei1.txt datei2.txt > datei.txt} verkettet werden. Außerdem kann man mit einem Aufruf in der Art \texttt{cat datei.txt | kommando} Daten an ein Programm übergeben, das nur von der Standardeingabe lesen kann (Filter). \texttt{cat} verfügt über eine Reige von Parametern, um die Ausgabe zu formatieren, so können mit \texttt{-n} bzw. \texttt{-b} die Zeilen nummeriert werden, oder mit \texttt{-s} mehrere Zeilen zu einer einzigen zusammengefaßt werden. \index{cat=\texttt{cat}|)} \subsection{chpasswd}\label{script}\index{chpasswd=\texttt{chpasswd}|(textbf} Mit diesem Kommando bietet sich dem Administrator des Systems die Möglichkeit, scriptgesteuert die Paßwörter für neue Benutzer zu vergeben. Manuell ändert man ein Paßwort mit dem Kommando \texttt{passwd}\index{passwd=\texttt{passwd}}, allerdings löscht (flusht) dieses Programm die Standard-Eingabe, bevor es das neue Paßwort erwartet. Somit lassen sich Paßwörter mit \texttt{passwd} nur interaktiv ändern\footnote{Es gibt auch einen anderen Weg: Man kann \texttt{passwd} auch mittels \texttt{expect} fernsteuern. Allerdings ist diese Methode weniger elegant.}. Das Kommando wird in der Form \texttt{echo }\textit{name}\texttt{:}\textit{pass}\texttt{ | chpasswd} aufgerufen. Es ist auch möglich, dem Programm eine Datei mit vielen Name / Paßwort-Kombinationen an die Standard-Eingabe zu übergeben: \texttt{cat }\textit{passwoerter.txt}\texttt{ | chpasswd} Allerdings sollte dabei aus Sicherheitsgründen darauf geachtet werden, daß diese Datei nicht allgemein lesbar ist. \index{chpasswd=\texttt{chpasswd}|)} \subsection{dirname}\label{dirname}\index{dirname=\texttt{dirname}|(textbf} Analog zu \texttt{basename} gibt \texttt{dirname} nur die Pfad-Komponente einer angegebenen Datei zurück. \index{dirname=\texttt{dirname}|)} \subsection{echo}\label{echo}\index{echo=\texttt{echo}|(textbf} Dies ist wohl der grundlegendste Befehl, der in einem Skript verwendet werden kann. Er ist die Voraussetzung, um eines der wichtigsten Werkzeuge der Kybernetik auch mittels eines Shell-Skriptes effizient umzusetzen: Hello World. :-) Die eigentliche Aufgabe dieses Befehls dürfte jedem bekannt sein, der sich bis zu dieser Stelle durchgearbeitet hat. Allerdings wissen viele nicht, daß auch der echo-Befehl über Parameter verfügt. Zumindest zwei davon erweisen sich in der Praxis oft als sehr hilfreich: \LTXtable{\textwidth}{tab_kommandos_echo_parameter.tex} \index{echo=\texttt{echo}|)} \subsection{expr} TODO!!! \subsection{grep}\label{grep}\index{grep=\texttt{grep}|(textbf} Das Tool \texttt{grep} stammt aus dem Standard-Repertoire eines jeden Systemadministrators. Mit seiner Hilfe kann in einer oder mehreren Dateien, oder eben auch in einem Datenstrom nach dem Auftreten bestimmter regulärer Ausdrücke (siehe \ref{mustererkennung}) gesucht werden. TODO!!! Die folgende Tabelle stellt einige der vielen Parameter vor: \LTXtable{\textwidth}{tab_kommandos_grep_parameter.tex} 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: \texttt{ps aux | grep }\textit{prozessname}\texttt{ \&\& echo \dq}\textit{läuft schon}\texttt{\dq} 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}-Kommando sich quasi selbst. Abhilfe schafft das folgende Konstrukt: \texttt{ps aux | grep \dq}\textit{[p]rozessname}\texttt{\dq~\&\& echo \dq}\textit{läuft schon}\texttt{\dq} 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{grep=\texttt{grep}|)} \subsection{head}\label{head}\index{head=\texttt{head}|(textbf} \texttt{head} ist das Gegenstück zu \texttt{tail} (Siehe \ref{tail}). Hier werden allerdings nicht die letzten Zeilen angezeigt, sondern die ersten. \index{head=\texttt{head}|)} \subsection{kill} TODO!!! \subsection{printf} TODO!!! \subsection{read}\label{read}\index{read=\texttt{read}|(textbf} Mit dem Kommando \texttt{read} kann man Eingaben von der Standard-Eingabe\index{Standard-Eingabe} lesen. Dabei wird üblicherweise einer oder mehrere Variablennamen übergeben. Dem ersten Namen wird das erste eingegebene Wort zugewiesen, dem zweiten das zweite Wort usw. Dem letzen Variablennamen wird der verbleibende Rest der Eingabezeile zugewiesen. Wenn also nur ein Variablenname angegeben wird, erhält dieser die komplette Eingabezeile. Wenn weniger Worte gelesen werden als Variablen angegeben sind, enthalten die verbleibenden Variablen leere Werte. Als Wort-Trennzeichen dienen alle Zeichen, die in der vordefinierten Variable \texttt{\$IFS} enthalten sind (siehe Seite \pageref{IFS}). Wenn keine Variablennamen angegeben werden, wird die Eingabe in der Variable \texttt{REPLY} abgelegt. Normalerweise wird eine Eingabezeile mit einem Newline abgeschlossen. Mit dem Parameter \texttt{-d} ist es möglich, ein anderes Zeilenendezeichen anzugeben. Beispielsweise liest \texttt{read -d \dq~\dq~var} alle Zeichen bis zum ersten Leerzeichen in die Variable \texttt{var} ein. Wenn nur eine bestimmte Zahl von Zeichen gelesen werden soll, kann diese durch den Parameter \texttt{-n} angegeben werden. Der Befehl \texttt{read -n 5 var} liest die ersten fünf Zeichen in die Variable \texttt{var} ein. Mit dem Parameter \texttt{-p} kann man einen Prompt, also eine Eingabeaufforderung ausgeben lassen. \texttt{read -p \dq{}Gib was ein:\dq~var} schreibt also erst den Text \textit{Gib was ein:} auf das Terminal, bevor die Eingaben in die Variable \texttt{var} übernommen werden. Dieser Prompt wird nur an einem interaktiven Terminal ausgegeben, also nicht in einem Skript das seine Eingaben aus einer Datei oder aus einem Stream erhält. Sonderzeichen können während der Eingabe normalerweise mittels eines Backslash vor der Interpretation geschützt werden. Ein Backslash vor einem Newline bewirkt also eine mehrzeilige Eingabe. Dieses Verhalten kann mit dem Parameter \texttt{-r} abgeschaltet werden. Wenn die Eingabe von einem Terminal kommt und nicht auf dem Bildschirm erscheinen soll, zum Beispiel bei Paßwortabfragen, kann die Ausgabe mit dem Parameter \texttt{-s} (Silent) unterdrückt werden. Mit \texttt{-t} kann ein Time-Out definiert werden, nach dessen Ablauf das Kommando mit einem Fehler abbricht. Dieser Parameter ist nur bei interaktiver Eingabe oder beim Lesen aus einer Pipe aktiv. Der Rückgabewert des \texttt{read}-Kommandos ist 0, es sei denn es trat ein Timeout oder ein EOF auf. \index{read=\texttt{read}|)} \subsection{script}\label{script}\index{script=\texttt{script}|(textbf} Dieses Kommando eignet sich vorzüglich für das Debuggen fertiger Skripte. Man ruft es in Verbindung mit einem Dateinamen auf. Dieser Aufruf startet eine neue Shell, in der man beliebige Kommandos ausführen kann. Wenn man fertig ist, beendet man den script-Befehl durch die Eingabe von \texttt{exit}, \texttt{logout} oder Druck der Tastenkombination \Ovalbox{CTRL}+\Ovalbox{d} (EOF). Script schreibt alle Ein- und Ausgaben die an dem Terminal vorgenommen werden in die angegebene Datei. So kann man auch interaktive Skripte relativ leicht debuggen, da sowohl Ein- als auch Ausgaben in dem Logfile sichtbar sind. \index{script=\texttt{script}|)} \subsection{sed} TODO!!! \subsection{seq} TODO!!! \subsection{sleep} TODO!!! \subsection{sort} TODO!!! \subsection{tail}\label{tail}\index{tail=\texttt{tail}|(textbf} Der Befehl \texttt{tail} gibt die letzten zehn Zeilen einer Datei aus. Wenn kein Dateiname (oder ein \texttt{-}) angegeben wird, liest \texttt{tail} von der Standard-Eingabe. Man kann die Anzahl der ausgegebenen Zeilen mit dem Parameter \texttt{-l} steuern. Mit dem Parameter \texttt{-f} (follow) gibt \texttt{tail} neue Zeilen aus, sobald sie an die Datei angehängt werden. \index{tail=\texttt{tail}|)} \subsection{tee} TODO!!! \subsection{touch}\label{touch}\index{touch=\texttt{touch}|(textbf} Mit diesem Kommando kann man einerseits Dateien anlegen wenn sie nicht existieren, und andererseits die Änderungs- und Zugriffszeiten einer Datei ändern. Ohne die Angabe weiterer Parameter wird die Datei erzeugt wenn sie nicht existierte, bzw. in ihrer Änderungs- und Zugriffszeit auf die aktuelle Zeit gesetzt. Mit dem Parameter \texttt{-a} wird nur die Zugriffs-, mit \texttt{-m} nur die Änderungszeit gesetzt. Mit \texttt{-c} kann die Erstellung einer neuen Datei unterdrückt werden. Die eingesetzte Zeit kann auch durch die Parameter \texttt{-t} bzw. \texttt{-d} angegeben werden. Mit \texttt{-r} kann die Zeit der einer angegebenen Referenzdatei angepaßt werden. \index{touch=\texttt{touch}|)} \subsection{wc} TODO!!! \subsection{who} TODO!!!