% $Id$ \chapter{Wie sieht ein Shell-Skript aus?} Wie schon erwähnt, kann ein Shell-Skript beinahe alles, was eine `richtige' Programmiersprache auch kann. Bei der Entwicklung sollte man nur bedenken, daß gerade die Aus\-füh\-rung von externen Kommandos~--~und das ist eine der Standard-Techniken bei der Shell-Pro\-gram\-mie\-rung~--~nur sehr langsam vonstatten geht. Für Anwendungen bei denen z. B. viele Rechnungen oder Stringbearbeitungen gemacht werden müssen, sollte man also ggf. die Benutzung einer anderen Sprache, beispielsweise Perl\index{Perl}, in Erwägung ziehen. In der Shell stehen viele Mechanismen zur Verfügung, die auch aus anderen Sprachen bekannt sind. Um den Umfang dieses Dokuments nicht zu sprengen, werden an dieser Stelle nur die wichtigsten vorgestellt. \section{HowTo} Zunächst soll die Frage geklärt werden, wie man überhaupt ein ausführbares Shell-Skript schreibt. Dabei wird vorausgesetzt, daß dem Benutzer der Umgang mit mindestens einem Texteditor\index{Texteditor} (\texttt{vi}\index{vi=\texttt{vi}}, \texttt{emacs}\index{emacs=\texttt{emacs}} etc.) bekannt ist. Zunächst muß mit Hilfe des Editors eine Textdatei angelegt werden, in die der `Quelltext' geschrieben wird. Dabei muß darauf geachtet werden, daß sich keine CR/LF-Zei\-len\-um\-brü\-che einschleichen, wie dies leicht bei der Benutzung von MS-DOS bzw. Windows-Systemen zur Bearbeitung von Skripten über das Netzwerk passieren kann. Wie der Quelltext aussieht, sollte man anhand der folgenden Abschnitte und der Beispiele im Anhang erkennen können. Beim Schreiben sollte man nicht mit den Kommentaren\index{Kommentar} geizen, da ein Shell-Skript auch schon mal sehr unleserlich werden kann. Nach dem Abspeichern der Datei unter einem geeigneten Namen\footnote{Bitte \emph{nicht} den Namen \texttt{test}\index{test=\texttt{test}} verwenden. Es existiert ein Unix-Systemkommando mit diesem Namen. Dieses steht fast immer eher im Pfad, d. h. beim Kommando \texttt{test} würde nicht das eigene Skript ausgeführt, sondern das Systemkommando. Dies ist einer der häufigsten und zugleich einer der verwirrendsten Anfängerfehler. Mehr zu dem \texttt{test}-Kommando unter \ref{bedingungen}.} muß die sie ausführbar gemacht werden. Das geht mit dem Unix-Kommando \texttt{chmod}\index{chmod=\texttt{chmod}} und wird in Abschnitt \ref{chmod} ausführlich beschrieben. An dieser Stelle reicht uns ein Aufruf in der Form \lstinline/chmod 755 name/, um das Skript für alle Benutzer ausführbar zu machen. Dann kann das Skript gestartet werden. Da sich aus Sicherheitsgründen auf den meisten Systemen das aktuelle Verzeichnis nicht im Pfad des Benutzers befindet, muß man der Shell noch mitteilen, wo sie zu suchen hat: Mit \lstinline|./name| wird versucht, im aktuellen Verzeichnis (\lstinline|./|) ein Programm namens \lstinline|name| auszuführen. Auf den meisten Systemen befindet sich im Pfad ein Verweis auf das Verzeichnis \texttt{bin} unterhalb des Home-Verzeichnisses eines Benutzers. Das bedeutet daß man Skripte die immer wieder benutzt werden sollen dort ablegen kann, so daß sie auch ohne eine Pfadangabe gefunden werden. Wie der Pfad genau aussieht kann man an der Shell durch Eingabe von \lstinline/echo $PATH/\index{\$PATH=\texttt{\$PATH}} herausfinden. \section{Fehlersuche}\label{fehlersuche}\index{Fehlersuche|(}\index{debuggen|(} Es gibt für Shell-Skripte keine wirklichen Debugger, aber trotzdem verfügt man über einige bewährte Methoden zum Aufspüren von Fehlern: \begin{itemize} \item Debug-Ausgaben: Das wohl einfachste Mittel um herauszufinden was im Skript vor sich geht sind wohl regelmäßige Debug-Ausgaben. Dazu fügt man einfach an `strategisch wichtigen' Punkten im Skript \texttt{echo}-Zeilen ein, die Auskunft über den Status geben. \item Syntax-Check: Wenn man das Skript in der Form \lstinline|sh -n ./skriptname| aufruft, wird es nicht wirklich ausgeführt. Lediglich die Syntax der Kommandos wird geprüft. Diese Methode findet natürlich keine logischen Fehler, und selbst wenn dieser Aufruf ohne Probleme durchläuft kann sich zur Laufzeit noch ein anderer Fehler einschleichen. \item \texttt{set -x}: Wenn in einem Skript der Aufruf \lstinline|set -x| abgesetzt wird, gibt die Shell jede Zeile nach der Expandierung aber vor der Ausführung aus. Dadurch ist klar ersichtlich wann welche Kommandos mit welchen Parametern ausgeführt werden. Um den Effekt wieder aufzuheben benutzt man \lstinline|set +x|. Man kann die Option auch auf das komplette Skript anwenden ohne sie in das Skript einbauen zu müssen. Dazu startet man das Skript nicht einfach durch \lstinline|./skriptname| sondern durch \lstinline|sh -x ./skriptname|. \item \texttt{set -v}: Dies funktioniert genau wie \lstinline|set -x|, auch der Aufruf von der Kommandozeile über \lstinline|sh -v ./skriptname| funktioniert. Diese Option gibt jede Zeile vor der Ausführung aus, allerdings im Gegensatz zu \texttt{-x} nicht in der expandierten sondern in der vollen Form. \item \texttt{set -e}: Alle gängigen Shell-Kommandos liefern einen Rückgabewert, der Auskunft über Erfolg oder Mißerfolg gibt (siehe Abschnitt \ref{exitcode}). Normalerweise liegt es beim Programmierer, diese Werte zu interpretieren. Setzt man aber mit dem Schalter \texttt{-e} den sogenannten errexit-Modus, beendet die Shell das Skript sobald ein Kommando sich mit einem Rückgabewert ungleich 0 beendet. Ausnahmen gibt es lediglich, wenn das betroffene Kommando in ein Konstrukt wie \texttt{while}, \texttt{until} oder \texttt{if} eingebunden ist. Auch wenn der Rückgabewert mittels \texttt{\&\&} oder \texttt{||} verarbeitet wird, beendet sich die Shell nicht. \item System-Log: Für das direkte Debuggen ist dieser Weg weniger geeignet, aber gerade in unbeobachtet laufenden Skripten sollte man unerwartete Zustände oder besondere Ereignisse im System-Log festhalten. Dies geschieht mit dem Kommando \texttt{logger}, das in Abschnitt \ref{logger} beschrieben wird. \item \texttt{script}: Mit dem Kommando \texttt{script} kann eine Sitzung an der Shell vollständig protokolliert werden, inclusive aller Ein- und Ausgaben. Das umfaßt sogar Drücke auf die Pfeiltasten oder auf Backspace. So kann auch eine längere Sitzung mit vielen Ein- und Ausgaben nach dem Testlauf in aller Ruhe analysiert werden. Das Kommando wird in Abschnitt \ref{script} beschrieben. \item \texttt{tee}: Wenn Ausgaben eines Kommandos durch den Filter \texttt{tee} geschoben werden, können sie in einer Datei mitgeschrieben werden. Auch diese Variante bietet einen streßfreien Blick auf unter Umständen sehr lange und komplexe Ausgaben. Abschnitt \ref{tee} gibt weitere Hinweise zu dem Kommando. \item Variablen `tracen': Das Kommando \texttt{trap} (Abschnitt \ref{trap}) reagiert auf Signale. Die Shell erzeugt nach jedem Kommando das Signal DEBUG, so daß mit dem folgenden Kommando dafür gesorgt werden kann, daß der Inhalt einer Variablen nach jedem Kommando ausgegeben wird: \lstinline|trap 'echo "Trace> \$var = \"$var\""' DEBUG| \end{itemize} \index{Fehlersuche|)}\index{debuggen|)} \section{Rückgabewerte}\label{exitcode}\index{Rückgabewert|(textbf}\index{Exit-Code|see{Rückgabewert}}\index{Exit-Status|see{Rückgabewert}} Wenn unter Unix ein Prozeß beendet wird, gibt er einen Rückgabewert (auch Exit-Code oder Exit-Status genannt) an seinen aufrufenden Prozeß zurück. So kann der Mutterprozeß kontrollieren, ob die Ausführung des Tochterprozesses ohne Fehler beendet wurde. In einigen Fällen (z. B. \texttt{grep}\index{grep=\texttt{grep}}) werden unterschiedliche Exit-Codes für unterschiedliche Ereignisse benutzt. Dieser Rückgabewert wird bei der interaktiven Benutzung der Shell nur selten benutzt, da Fehlermeldungen direkt vom Benutzer abgelesen werden können. Aber in der Programmierung von Shell-Skripten ist er von unschätzbarem Wert. So kann das Skript automatisch entscheiden, ob bestimmte Aktionen ausgeführt werden sollen, die von anderen Aktionen ab\-hän\-gen. Beispiele dazu sieht man bei der Beschreibung der Kommandos \texttt{if}\index{if=\texttt{if}} (\ref{if}), \texttt{case}\index{case=\texttt{case}} (\ref{case}), \texttt{while}\index{while=\texttt{while}} (\ref{while}) und \texttt{until}\index{until=\texttt{until}} (\ref{until}), sowie in dem Abschnitt über Befehlsformen (\ref{befehlsformen}). In der Bourne-Shell wird der Exit-Code des letzten aufgerufenen Programms in der Variable \texttt{\$?}\index{\$?=\texttt{\$?}} abgelegt. Üblicherweise geben Programme den Wert 0 zurück, bei irgendwelchen Problemen einen von 0 verschiedenen Wert. Das wird im folgenden Beispiel deutlich: \begin{lstlisting} $ cp datei /tmp $ echo $? 0 $ cp datie /tmp cp: datie: Datei oder Verzeichnis nicht gefunden $ echo $? 1 \end{lstlisting} Normalerweise wird man den Exit-Code nicht in dieser Form abfragen. Sinnvoller ist folgendes Beispiel, in dem eine Datei erst gedruckt wird, und dann~--~falls der Ausdruck erfolgreich war~--~gelöscht wird: \lstinline/$ lpr datei && rm datei/\index{\&\&=\texttt{\&\&}} Näheres zur Verknüpfung von Aufrufen steht im Kapitel über Befehlsformen (\ref{befehlsformen}). Beispiele zur Benutzung von Rückgabewerten in Schleifen finden sich im Anhang unter \ref{beisp_schleifen_exitcode}. Auch Shell-Skripte können einen Rückgabewert an aufrufende Prozesse zurückgeben. Wie das geht, steht in dem Abschnitt zu \texttt{exit} (\ref{exit}). \index{Rückgabewert|)} \section{Variablen}\index{Variablen|(textbf} In einem Shell-Skript hat man~--~genau wie bei der interaktiven Nutzung der Shell~--~Möglichkeiten, über Variablen zu verfügen. Anders als in den meisten modernen Programmiersprachen gibt es aber keine Datentypen\index{Datentypen} wie Ganzzahlen, Fließkommazahlen oder Strings\footnote{Bei einigen modernen Shells (\texttt{csh}\index{C-Shell}, \texttt{tcsh}\index{TENEX-C-Shell}, \texttt{ksh}\index{Korn-Shell}, \texttt{bash}\index{Bourne-Again-Shell}, \texttt{zsh}\index{Z-Shell}...) hat man die Möglichkeit, Variablentypen zu vereinbaren. In der Bourne-Shell\index{Bourne-Shell} nicht.}. Alle Variablen werden als String gespeichert, wenn die Variable die Funktion einer Zahl übernehmen soll, dann muß das verarbeitende Programm die Variable entsprechend interpretieren\footnote{Für arithmetische Operationen steht das Programm \texttt{expr}\index{expr=\texttt{expr}} zur Verfügung (siehe Zählschleifen-Beispiel unter \ref{while})}. Man muß bei der Benutzung von Variablen sehr aufpassen, wann die Variable expandiert\footnote{Mit \emph{Expansion}\index{Expansion} ist das Ersetzen des Variablennamens durch den Inhalt gemeint} wird und wann nicht. Grundsätzlich werden Variablen während der Ausführung des Skriptes immer an den Stellen ersetzt, an denen sie stehen. Das passiert in jeder Zeile, unmittelbar bevor sie ausgeführt wird. Es ist also auch möglich, in einer Variable einen Shell-Befehl abzulegen. Im Folgenden kann dann der Variablenname an der Stelle des Befehls stehen. Um die Expansion einer Variable zu verhindern, benutzt man das Quoting\index{Quoting} (siehe unter \ref{quoting}). Wie aus diversen Beispielen hervorgeht, belegt man eine Variable, indem man dem Namen mit dem Gleichheitszeichen einen Wert zuweist. Dabei darf zwischen dem Namen und dem Gleichheitszeichen keine Leerstelle stehen, ansonsten erkennt die Shell den Variablennamen nicht als solchen und versucht, ein gleichnamiges Kommando auszuführen~--~was meistens durch eine Fehlermeldung quittiert wird. Wenn man auf den Inhalt einer Variablen zugreifen möchte, leitet man den Variablennamen durch ein \texttt{\$}-Zeichen ein. Alles was mit einem \texttt{\$} anfängt wird von der Shell als Variable angesehen und entsprechend behandelt (expandiert). \index{Variablen|)} \section{Vordefinierte Variablen}\label{vordefinierte_variablen}\index{Variablen}\index{vordefinierte Variablen} \index{\$n=\texttt{\$}$n$|(textbf} \index{\$*=\texttt{\$*}|(textbf} \index{\$@=\texttt{\$@}|(textbf} \index{\$\#=\texttt{\$\#}|(textbf} \index{\$?=\texttt{\$?}|(textbf} \index{\$\$=\texttt{\$\$}|(textbf} \index{\$!!=\texttt{\$!!}|(textbf} \index{\$ERRNO=\texttt{\$ERRNO}|(textbf} \index{\$IFS=\texttt{\$IFS}|(textbf} \index{\$PATH=\texttt{\$PATH}|(textbf} \index{\$PWD=\texttt{\$PWD}|(textbf} \index{\$OLDPWD=\texttt{\$OLDPWD}|(textbf} \index{ERRNO=\texttt{ERRNO}|see{\$ERRNO}} \index{IFS=\texttt{IFS}|see{\$IFS}} \index{PATH=\texttt{PATH}|see{\$PATH}} \index{PWD=\texttt{PWD}|see{\$PWD}} \index{OLDPWD=\texttt{OLDPWD}|see{\$OLDPWD}} Es gibt eine Reihe von vordefinierten Variablen, deren Benutzung ein wesentlicher Bestandteil des Shell-Programmierens ist. Die wichtigsten eingebauten Shell-Variablen sind:\nopagebreak \LTXtable{\textwidth}{tab_vordefinierte_variablen.tex} Die Variable \texttt{\$IFS} enthält per Default die Blank-Zeichen, also Newline, Space und Tab. Man kann sie aber auch mit anderen Zeichen überschreiben. Diese werden immer dann als Trennzeichen benutzt, wenn ein String in mehrere Teile zerlegt werden soll, also beispielsweise in \texttt{for}-Schleifen oder beim zeilenweisen Einlesen mit \texttt{read}. Ein gutes Beispiel gibt es in dem Beispielskript zu \texttt{printf} (Abschnitt \ref{printf}). \texttt{\$ERRNO}, \texttt{\$PWD} und \texttt{\$OLDPWD} werden nicht von jeder Shell gesetzt. \index{\$n=\texttt{\$}$n$|)} \index{\$*=\texttt{\$*}|)} \index{\$@=\texttt{\$@}|)} \index{\$\#=\texttt{\$\#}|)} \index{\$?=\texttt{\$?}|)} \index{\$\$=\texttt{\$\$}|)} \index{\$!!=\texttt{\$!!}|)} \index{\$ERRNO=\texttt{\$ERRNO}|)} \index{\$IFS=\texttt{\$IFS}|)} \index{\$PATH=\texttt{\$PATH}|)} \index{\$PWD=\texttt{\$PWD}|)} \index{\$OLDPWD=\texttt{\$OLDPWD}|)} \section{Variablen-Substitution}\index{Variablen>-Substitution|(textbf}\index{Substitution|see{Variablen-Subst.}}\index{Variablen|(textbf} \index{!==\texttt{!=}|(textbf}\index{\$\{Variable\}=\texttt{\$\{}\textsl{Variable}\texttt{\}}|(textbf}\index{\$\{Variable:-Wert\}=\texttt{\$\{}\textsl{Variable}\texttt{:-}\textsl{Wert}\texttt{\}}|(textbf}\index{\$\{Variable:=Wert\}=\texttt{\$\{}\textsl{Variable}\texttt{:=}\textsl{Wert}\texttt{\}}|(textbf}\index{\$\{Variable:?Wert\}=\texttt{\$\{}\textsl{Variable}\texttt{:?}\textsl{Wert}\texttt{\}}|(textbf}\index{\$\{Variable:+Wert\}=\texttt{\$\{}\textsl{Variable}\texttt{:+}\textsl{Wert}\texttt{\}}|(textbf} Unter Variablen-Substitution versteht man verschiedene Methoden um die Inhalte von Variablen zu benutzen. Das umfaßt sowohl die einfache Zuweisung eines Wertes an eine Variable als auch einfache Möglichkeiten zur Fallunterscheidung. In den fortgeschritteneren Shell-Versionen (\texttt{bash}\index{Bourne-Again-Shell}, \texttt{ksh}\index{Korn-Shell}) existieren sogar Möglichkeiten, auf Substrings von Variableninhalten zuzugreifen. In der Standard-Shell benutzt man für einfache Aufgaben üblicherweise Tools wie \texttt{cut}, \texttt{basename}\index{basename=\texttt{basename}} oder \texttt{dirname}; komplexe Bearbeitungen erledigt der Stream-Editor \texttt{sed}\index{sed=\texttt{sed}}. Einleitende Informationen dazu finden sich im Kapitel über die Mustererkennung (\ref{mustererkennung}). Die folgenden Mechanismen stehen in der Standard-Shell bereit, um mit Variablen zu hantieren. Bei allen Angaben ist der Doppelpunkt optional. Wenn er aber angegeben wird, muß die \textsl{Variable} einen Wert enthalten. \nopagebreak \LTXtable{\textwidth}{tab_variablen_substitution.tex} \medskip\emph{Beispiele:}\nopagebreak \LTXtable{\textwidth}{tab_beisp_variablen_substitution.tex} \index{!==\texttt{!=}|)}\index{\$\{Variable\}=\texttt{\$\{}\textsl{Variable}\texttt{\}}|)}\index{\$\{Variable:-Wert\}=\texttt{\$\{}\textsl{Variable}\texttt{:-}\textsl{Wert}\texttt{\}}|)}\index{\$\{Variable:=Wert\}=\texttt{\$\{}\textsl{Variable}\texttt{:=}\textsl{Wert}\texttt{\}}|)}\index{\$\{Variable:?Wert\}=\texttt{\$\{}\textsl{Variable}\texttt{:?}\textsl{Wert}\texttt{\}}|)}\index{\$\{Variable:+Wert\}=\texttt{\$\{}\textsl{Variable}\texttt{:+}\textsl{Wert}\texttt{\}}|)} \index{Variablen>-Substitution|)}\index{Variablen|)} \section{Quoting}\index{Quoting|(textbf}\label{quoting} \index{Anführungszeichen|(textbf}\index{Ticks|(textbf}\index{Backslash|(textbf}\index{;=\texttt{;}|(textbf}\index{\&=\texttt{\&}|(textbf}\index{( )=\texttt{( )}|(textbf}\index{|=\texttt{|}|(textbf}\index{<=\texttt{<}|(textbf}\index{!>=\texttt{!>}|(textbf}\index{!>\&=\texttt{!>\&}|(textbf}\index{*=\texttt{*}|(textbf}\index{?=\texttt{?}|(textbf}\index{[ ]=\texttt{[ ]}|(textbf}\index{\~{}=\texttt{\~{}}|(textbf}\index{+=\texttt{+}|(textbf}\index{-=\texttt{-}|(textbf}\index{@=\texttt{@}|(textbf}\index{!!=\texttt{!!}|(textbf}\index{Backticks|(textbf}\index{\$=\texttt{\$}|(textbf}\index{[newline]=\texttt{[newline]}|(textbf}\index{[space]=\texttt{[space]}|(textbf}\index{[tab]=\texttt{[tab]}|(textbf} \index{' '=\texttt{' '}|see{Ticks}}\index{` `=\texttt{` `}|see{Backticks}}\index{\dq~\dq=\texttt{\dq~\dq}|see{Anführungszeichen}}\index{\textbackslash=\texttt{\textbackslash}|see{Backslash}} Dies ist ein sehr schwieriges Thema, da hier mehrere ähnlich aussehende Zeichen völlig verschiedene Effekte bewirken. Die Bourne-Shell unterscheidet allein zwischen drei verschiedenen Anführungszeichen. Das Quoten dient dazu, bestimmte Zeichen mit einer Sonderbedeutung vor der Shell zu `verstecken' um zu verhindern, daß diese expandiert (ersetzt) werden. Die folgenden Zeichen haben eine spezielle Bedeutung innerhalb der Shell:\nopagebreak \LTXtable{\textwidth}{tab_quoting_sonderzeichen.tex} Die folgenden Zeichen können zum Quoten verwendet werden:\nopagebreak \LTXtable{\textwidth}{tab_quoting_zeichen.tex} \medskip\emph{Beispiele:}\nopagebreak \begin{lstlisting} $ echo 'Ticks "schützen" Anführungszeichen' Ticks "schützen" Anführungszeichen $ echo "Ist dies ein \"Sonderfall\"?" Ist dies ein "Sonderfall"? $ echo "Sie haben `ls | wc -l` Dateien in `pwd`" Sie haben 43 Dateien in /home/rschaten $ echo "Der Wert von \$x ist $x" Der Wert von $x ist 100 \end{lstlisting} \index{Anführungszeichen|)}\index{Ticks|)}\index{Backslash|)}\index{;=\texttt{;}|)}\index{\&=\texttt{\&}|)}\index{( )=\texttt{( )}|)}\index{|=\texttt{|}|)}\index{<=\texttt{<}|)}\index{!>=\texttt{!>}|)}\index{!>\&=\texttt{!>\&}|)}\index{*=\texttt{*}|)}\index{?=\texttt{?}|)}\index{[ ]=\texttt{[ ]}|)}\index{\~{}=\texttt{\~{}}|)}\index{+=\texttt{+}|)}\index{-=\texttt{-}|)}\index{@=\texttt{@}|)}\index{!!=\texttt{!!}|)}\index{Backticks|)}\index{\$=\texttt{\$}|)}\index{[newline]=\texttt{[newline]}|)}\index{[space]=\texttt{[space]}|)}\index{[tab]=\texttt{[tab]}|)} \index{Quoting|)} \section{Meta-Zeichen}\index{Meta-Zeichen|(textbf}\index{Wildcards|see{Metazeichen}}\index{Joker-Zeichen|see{Metazeichen}}\index{Platzhalter|see{Metazeichen}}\index{Globbing|see{Metazeichen}}\label{metazeichen} \index{*=\texttt{*}|(textbf}\index{?=\texttt{?}|(textbf}\index{[abc]=\texttt{[}\textsl{abc}\texttt{]}|(textbf}\index{[a-q]=\texttt{[}\textsl{a}\texttt{-}\textsl{q}\texttt{]}|(textbf}\index{[!!abc]=\texttt{[!!}\textsl{abc}\texttt{]}|(textbf}\index{Dateinamen|(textbf} \index{\~{}=\texttt{\~{}}|(textbf}\index{\~{}name=\texttt{\~{}}\textsl{name}|(textbf}\index{\~{}+=\texttt{\~{}+}|(textbf}\index{\~{}-=\texttt{\~{}-}|(textbf} Bei der Angabe von Dateinamen können eine Reihe von Meta-Zeichen\footnote{Meta-Zeichen werden auch Wildcards, Joker-Zeichen oder Platzhalter genannt. Meint man die Expansion der Meta-Zeichen zu Dateinamen ist auch von `Globbing' die Rede.} verwendet werden, um mehrere Dateien gleichzeitig anzusprechen oder um nicht den vollen Dateinamen ausschreiben zu müssen. Die wichtigsten Meta-Zeichen sind:\nopagebreak \LTXtable{\textwidth}{tab_metazeichen.tex} \texttt{\~}, \texttt{\~{}}\textsl{name}, \texttt{\~{}+} und \texttt{\~{}-} werden nicht von jeder Shell unterstützt. \medskip\emph{Beispiele:}\nopagebreak \begin{lstlisting} # Alle Dateien listen, die mit 'neu' anfangen: $ ls neu* # 'neuX', 'neu4', aber nicht 'neu10' listen: $ ls neu? # Alle Dateien listen, die mit einem Grossbuchstaben zwischen D und R # anfangen - Natuerlich ist die Shell auch hier Case-Sensitive: $ ls [D-R]* \end{lstlisting} Hier ist anzumerken, daß Hidden Files (Dateien, deren Name mit einem Punkt beginnt) nicht durch ein einfaches \texttt{*} erfaßt werden, sondern nur durch das Suchmuster \texttt{.*}. \index{*=\texttt{*}|)}\index{?=\texttt{?}|)}\index{[abc]=\texttt{[}\textsl{abc}\texttt{]}|)}\index{[a-q]=\texttt{[}\textsl{a}\texttt{-}\textsl{q}\texttt{]}|)}\index{[!!abc]=\texttt{[!!}\textsl{abc}\texttt{]}|)}\index{Dateinamen|)} \index{\~{}=\texttt{\~{}}|)}\index{\~{}name=\texttt{\~{}}\textsl{name}|)}\index{\~{}+=\texttt{\~{}+}|)}\index{\~{}-=\texttt{\~{}-}|)} \index{Meta-Zeichen|)} \section{Mustererkennung}\index{Mustererkennung|(textbf}\label{mustererkennung} \index{ed=\texttt{ed}|(textbf}\index{ex=\texttt{ex}|(textbf}\index{vi=\texttt{vi}|(textbf}\index{sed=\texttt{sed}|(textbf}\index{awk=\texttt{awk}|(textbf}\index{grep=\texttt{grep}|(textbf}\index{egrep=\texttt{egrep}|(textbf} \index{*=\texttt{*}|(textbf}\index{.=\texttt{.}|(textbf}\index{\^=\texttt{\^}|(textbf}\index{\$=\texttt{\$}|(textbf}\index{Backslash|(textbf}\index{[ ]=\texttt{[ ]}|(textbf}\index{\textbackslash( \textbackslash)=\texttt{\textbackslash( \textbackslash)}|(textbf}\index{\textbackslash\{ \textbackslash\}=\texttt{\textbackslash\{ \textbackslash\}}|(textbf}\index{\textbackslash< \textbackslash>=\texttt{\textbackslash< \textbackslash>}|(textbf}\index{+=\texttt{+}|(textbf}\index{?=\texttt{?}|(textbf}\index{|=\texttt{|}|(textbf}\index{( )=\texttt{( )}|(textbf}\index{Regulärer Ausdruck|(textbf} \index{\textbackslash n=\texttt{\textbackslash}\textsl{n}|(textbf}\index{\&=\texttt{\&}|(textbf}\index{\~{}=\texttt{\~{}}|(textbf}\index{\textbackslash u=\texttt{\textbackslash u}|(textbf}\index{\textbackslash U=\texttt{\textbackslash U}|(textbf}\index{\textbackslash l=\texttt{\textbackslash l}|(textbf}\index{\textbackslash L=\texttt{\textbackslash L}|(textbf}\index{\textbackslash E=\texttt{\textbackslash E}|(textbf}\index{\textbackslash e=\texttt{\textbackslash e}|(textbf} Man unterscheidet in der Shell-Programmierung zwischen den Meta-Zeichen\index{Meta-Zeichen}, die bei der Bezeichnung von Dateinamen eingesetzt werden und den Meta-Zeichen, die in mehreren Programmen Verwendung finden, um z. B. Suchmuster zu definieren. Diese Muster werden auch reguläre Ausdrücke (regular expression)\index{Regular Expression|see{Regulärer Ausdruck}}\index{Expression|see{Regulärer Ausdruck}}\index{Ausdruck|see{Regulärer Ausdruck}} genannt. Sie bieten wesentlich mehr Möglichkeiten als die relativ einfachen Wildcards für Dateinamen. In der folgenden Tabelle wird gezeigt, in welchen Unix-Tools welche Zeichen zur Ver\-fü\-gung stehen. Eine ausführlichere Beschreibung der Einträge findet sich auf Seite \pageref{beschreibung_der_muster}. \nopagebreak \LTXtable{\textwidth}{tab_mustererkennung_muster.tex} Bei einigen Tools (\texttt{ex}, \texttt{sed} und \texttt{ed}) werden zwei Muster angegeben: Ein Suchmuster (links) und ein Ersatzmuster (rechts). Nur die folgenden Zeichen sind in einem Ersatzmuster gültig:\nopagebreak \LTXtable{\textwidth}{tab_mustererkennung_ersatzmuster.tex} \medskip\emph{Sonderzeichen in Suchmustern:}\label{beschreibung_der_muster}\nopagebreak \LTXtable{\textwidth}{tab_mustererkennung_sonderzeichen.tex} \medskip\emph{Sonderzeichen in Ersatzmustern:}\nopagebreak \LTXtable{\textwidth}{tab_mustererkennung_ersatzsonderzeichen.tex} \medskip\emph{Beispiele:} Muster\nopagebreak \LTXtable{\textwidth}{tab_beisp_muster.tex} \medskip\emph{Beispiele:} egrep- oder awk-Muster\nopagebreak \LTXtable{\textwidth}{tab_beisp_muster_egrep.tex} \medskip\emph{Beispiele:} ex- oder vi-Muster\nopagebreak \LTXtable{\textwidth}{tab_beisp_muster_ex.tex} \medskip\emph{Beispiele:} sed- oder grep-Muster\nopagebreak \LTXtable{\textwidth}{tab_beisp_muster_sed.tex} \medskip\emph{Beispiele:} Suchen und Ersetzen mit \texttt{sed} und \texttt{ex}. Im Folgenden werden Leerzeichen durch \Ovalbox{SPACE} und Tabulatoren durch \Ovalbox{TAB} gekennzeichnet. Befehle für ex werden mit einem Doppelpunkt eingeleitet.\nopagebreak \LTXtable{\textwidth}{tab_beisp_sed-ex.tex} \index{ed=\texttt{ed}|)}\index{ex=\texttt{ex}|)}\index{vi=\texttt{vi}|)}\index{sed=\texttt{sed}|)}\index{awk=\texttt{awk}|)}\index{grep=\texttt{grep}|)}\index{egrep=\texttt{egrep}|)} \index{*=\texttt{*}|)}\index{.=\texttt{.}|)}\index{\^=\texttt{\^}|)}\index{\$=\texttt{\$}|)}\index{Backslash|)}\index{[ ]=\texttt{[ ]}|)}\index{\textbackslash( \textbackslash)=\texttt{\textbackslash( \textbackslash)}|)}\index{\textbackslash\{ \textbackslash\}=\texttt{\textbackslash\{ \textbackslash\}}|)}\index{\textbackslash< \textbackslash>=\texttt{\textbackslash< \textbackslash>}|)}\index{+=\texttt{+}|)}\index{?=\texttt{?}|)}\index{|=\texttt{|}|)}\index{( )=\texttt{( )}|)}\index{Regulärer Ausdruck|)} \index{\textbackslash n=\texttt{\textbackslash}\textsl{n}|)}\index{\&=\texttt{\&}|)}\index{\~{}=\texttt{\~{}}|)}\index{\textbackslash u=\texttt{\textbackslash u}|)}\index{\textbackslash U=\texttt{\textbackslash U}|)}\index{\textbackslash l=\texttt{\textbackslash l}|)}\index{\textbackslash L=\texttt{\textbackslash L}|)}\index{\textbackslash E=\texttt{\textbackslash E}|)}\index{\textbackslash e=\texttt{\textbackslash e}|)} \index{Mustererkennung|)} \section{Klammer-Expansion\label{klammerexpansion}\index{Klammer-Expansion|(textbf}\index{Brace Expansion|see{Klammer-Expansion}}} Dieser Mechanismus ist sehr praktisch, aber nur wenigen Programmierern bekannt. Er steht nicht in jeder Shell zur Verfügung. Über die Klammer-Expansion (Brace Expansion) können automatisch Strings generiert werden. Dabei wird ein Muster angegeben, nach dem neue Strings aufgebaut werden. Dieses Muster besteht aus einem Prefix, der allen erzeugten Strings vorangestellt wird, und einer in geschweifte Klammern eingebundenen und durch Komma getrennten Menge von String-Teilen. Dieses Konstrukt expandiert zu mehreren, durch Leerzeichen getrennten Strings, indem sämtliche möglichen Permutationen generiert werden. Die durch die Klammern angegebenen Mengen können auch verschachtelt werden. Dabei werden die Klammern von links nach rechts aufgelöst. Die Ergebnisse werden nicht sortiert, sondern in der Reihenfolge ihrer Erstellung zurückgegeben werden. Die Expansion von Klammern erfolgt vor der Behandlung aller anderen Ersetzungen. Auch eventuell vorhandenen Sonderzeichen bleiben in dem expandierten Text erhalten. So lassen sich auch Variablen durch diese Technik erzeugen. \medskip\emph{Beispiele:}\nopagebreak \begin{lstlisting} # Folgende Verzeichnisse erzeugen: # - /usr/local/src/bash/old # - /usr/local/src/bash/new # - /usr/local/src/bash/dist # - /usr/local/src/bash/bugs $ mkdir /usr/local/src/bash/{old,new,dist,bugs} # Die folgenden Dateien / Verzeichnisse dem Benutzer root zuweisen: # - /usr/ucb/ex # - /usr/ucb/edit # - /usr/lib/ex?.?* # - /usr/lib/how_ex # Dabei wird /usr/lib/ex?.?* noch weiter expandiert. $ chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}} \end{lstlisting} \index{Klammer-Expansion|)} \section{Arithmetik-Expansion\label{arithmetikexpansion}\index{Arithmetik-Expansion|(textbf}} Auch hier werden Klammern expandiert. Allerdings gleich doppelte Klammern. Mit einem Konstrukt in der Form \lstinline|i=$(($i + 1))| können einfache Berechnungen angestellt werden. Dabei wird der Ausdruck in den Klammern bewertet als ob er in doppelten An\-füh\-rungs\-zei\-chen stehen würde. Das bedeutet zum Einen, daß man auch mit Variablen rechnen kann, zum anderen macht es das Quoten des Sternchens überflüssig. Für komplexere Berechnungen steht das Tool \texttt{bc} (Siehe Abschnitt \ref{bc}) zur Ver\-fü\-gung. \index{Arithmetik-Expansion|)} \section{Eltern und Kinder: Prozeßordnung\label{prozessordnung}\index{Prozess|(textbf}\index{PID|see{Prozess}}\index{Parent-Prozess|see{Prozess}}\index{PPID|see{Prozess}}\index{Subshell|(textbf}} Jedes laufende Programm auf einem Unixoiden System besteht aus einem oder mehreren Prozessen, die jeweils eine eigene Prozeß-ID (PID) haben. Erzeugt ein Programm mehrere Prozesse, sind die zu einer Prozeßgruppe zusammengefaßt. Jeder laufende Prozeß\footnote{Es gibt eine Ausnahme: der Init-Prozeß, der immer die PID 1 hat, hat keine Eltern. Er stammt direkt vom Kernel ab.} verfügt über genau eine Parent-Prozeß-ID (PPID). Das ist die ID des Prozesses, der den jeweiligen Prozeß erzeugt hat. Man spricht in diesem Zusammenhang tatsächlich von Eltern- bzw. Kind-Prozessen. Diese Zusammenhänge lassen sich sehr schön durch die Ausgabe eines Kommandos wie \texttt{pstree} oder \lstinline|ps -efH| darstellen, letzteres zeigt auch gleich die PIDs und die PPIDs an. Wenn in einer Shell ein Kommando gestartet wird, ist es ein Kind dieser Shell. Wird ein Skript gestartet, öffnet es sich seine eigene Shell (siehe \ref{auswahl_der_shell}) und führt sich innerhalb dieser aus. Die Shell des Skriptes ist dabei ein Kind der interaktiven Shell, die einzelnen Kommandos des Skriptes sind Kinder der Skript-Shell. Eine solche Shell-in-Shell-Umgebung wird `Subshell' genannt, dieser Mechanismus~--~und somit auch der Begriff~--~tauchen immer wieder auf. Wichtig in diesem Zusammenhang ist das Verständnis für die Vererbung zwischen den beteiligten Prozessen. Wenn in einer Shell eine Variable definiert und exportiert wird, existiert diese auch für die Kind-Prozesse. Gleiches gilt beispielsweise für einen Verzeichnis-Wechsel. Umgekehrt gilt dies nicht: ein Prozeß kann die Umgebung des Parent-Prozesses nicht verändern. Das geschieht nicht zuletzt aus Sicherheitsgründen so. Will man die Änderungen eines Skriptes übernehmen~--~beispielsweise wenn ein Skript die Benutzerumgebung konfigurieren soll (.bashrc, .profile und Konsorten)~--~muß das explizit angefordert werden. Dazu ruft man es mit einem vorangestellten \texttt{source}\index{source=\texttt{source}} bzw. in der Kurzform mit einem vorangestellten Punkt auf. Weiteres zu dem Thema findet sich im Abschnitt \ref{source}. Besonders muß man diesen Umstand im Hinterkopf behalten, wenn mit Pipelines\index{Pipe} (siehe Abschnitt \ref{befehlsformen}) gearbeitet wird. Dabei werden auch Kommandos in Subshells ausgeführt, was dann dazu führt daß Variablen belegt werden die dann nach Ausführung der Pipeline plötzlich wieder leer sind. Die Abschnitte \ref{subshellschleifen} und \ref{daten_hochreichen} widmen sich diesem mitunter recht ärgerlichen Thema. \index{Prozess|)}\index{Subshell|)} \section{Programmablaufkontrolle} Bei der Shell-Programmierung verfügt man über ähnliche Konstrukte wie bei anderen Programmiersprachen, um den Ablauf des Programms zu steuern. Dazu gehören Funktionsaufrufe, Schleifen, Fallunterscheidungen und dergleichen.\nopagebreak \subsection{Kommentare\label{kommentare}\index{Kommentar|(textbf} (\texttt{\#})}\index{\#=\texttt{\#}|see{Kommentar}} Kommentare in der Shell beginnen immer mit dem Nummern-Zeichen (\verb\#\). Dabei spielt es keine Rolle, ob das Zeichen am Anfang der Zeile steht, oder hinter irgendwelchen Befehlen. Alles von diesem Zeichen bis zum Zeilenende wird nicht beachtet (bis auf eine Ausnahme~--~siehe unter \ref{auswahl_der_shell}). \index{Kommentar|)} \subsection{Auswahl der Shell (\texttt{\#!})}\label{auswahl_der_shell}\index{\#!!=\texttt{\#!!}|see{Shell / Auswahl der\ldots}}\index{Shell>Auswahl der\ldots|(textbf} In der ersten Zeile eines Shell-Skriptes sollte definiert werden, mit welchem Programm das Skript ausgeführt werden soll. Das System öffnet dann eine Subshell\index{Subshell} und führt das restliche Skript in dieser aus. Die Angabe erfolgt über eine Zeile in der Form \lstinline|#!/bin/sh|, wobei unter \lstinline|/bin/sh| die entsprechende Shell (in diesem Fall die Bourne-Shell) liegt. Dieser Eintrag wirkt nur dann, wenn er in der ersten Zeile und der ersten Spalte des Skripts steht. Dieser Mechanismus ist bei der Bourne-Shell nicht vorhanden, er wurde mit den moderneren Shells eingeführt um eben durch die Angabe von \lstinline|#!/bin/sh| die Bourne-Shell für die Ausführung von Shell-Skripten benutzen zu können. In der Bourne-Shell wirkt das führende \verb\#\ als Kommentarzeichen. \index{Shell>Auswahl der\ldots|)} \subsection{Null-Befehl (\texttt{:})}\label{null-befehl}\index{Null-Befehl|(textbf}\index{:=\texttt{:}|see{Null-Befehl}}\index{Doppelpunkt|see{Null-Befehl}} Dieser Befehl tut nichts, außer den Status 0 zurückzugeben. Er wird benutzt, um Endlosschleifen\index{Endlosschleife} zu schreiben (siehe unter \ref{while}), oder um leere Blöcke in \texttt{if}- oder \texttt{case}-Konstrukten\index{if=\texttt{if}}\index{case=\texttt{case}} möglich zu machen. \begin{lstlisting} if who | grep $1 > /dev/null; then # who: Liste der Benutzer # grep: Suche nach Muster : # tut nichts else echo "Benutzer $1 ist nicht angemeldet" fi \end{lstlisting} \subsection{Source (\texttt{.})}\label{source}\index{source=\texttt{source}|(textbf}\index{.=\texttt{.}|see{source}} Ein Shell-Skript kann in keiner Weise Einfluß auf die umgebende Shell nehmen. Das heißt, daß es beispielsweise nicht möglich ist, in einem Skript Variablen zu setzen, die dann in der aufrufenden Shell zur Verfügung stehen. Genauso wenig ist es möglich, daß ein Skript den Pfad ändert, in dem man sich befindet. Der Grund für dieses Verhalten ist die Systemsicherheit. Man will verhindern, daß ein Skript unbemerkt Änderungen an der Benutzerumgebung vornimmt. Wenn es aber doch gewünscht wird, daß ein Skript die Umgebung des Benutzers ändern kann, dann muß es mit dem Source-Kommando aufgerufen werden. Das wird in der Form \lstinline|source skriptname| bzw. \lstinline|. skriptname| angegeben. Er bewirkt ähnliches wie ein \verb\#include\ in der Programmiersprache C. Die `gesourcte' Datei wird eingelesen und ausgeführt, als ob ihr Inhalt an der Stelle des Befehls stehen würde. Diese Methode wird zum Beispiel beim Login in den Konfigurationsdateien des Benutzers (z. B. \verb\.profile\, \verb\.bashrc\) oder während des Bootvorgangs in den Init-Skripten benutzt, um immer wieder benötigte Funktionen (Starten eines Dienstes, Statusmeldungen auf dem Bildschirm etc.) in einer zentralen Datei pflegen zu können (siehe Beispiel unter~\ref{init-skript}). \index{source=\texttt{source}|)} \subsection{Funktionen}\label{funktionen}\index{Funktion|(textbf} Es ist in der Shell auch möglich, ähnlich wie in einer `richtigen' Programmiersprache Funktionen zu deklarieren und zu benutzen. Da die Bourne-Shell (\texttt{sh}) nicht über Aliase\index{Aliase} verfügt, können einfache Funktionen als Ersatz dienen. Der Rückgabewert einer Funktion ist gleich dem Rückgabewert des letzten in der Funktion aufgerufenen Kommandos, es sei denn man gibt mittels \texttt{return} (Siehe \ref{return}) explizit einen anderen Wert zurück. \medskip\emph{Beispiel:} Die Funktion gibt die Anzahl der Dateien im aktuellen Verzeichnis aus. Aufgerufen wird diese Funktion wie ein Befehl, also einfach durch die Eingabe von \texttt{count}. \begin{lstlisting} count () { ls -1 | wc -l # ls: Liste aller Dateien im Verzeichnis # Mit Parameter -1 einspaltig # wc: Word-Count, zählt mit Parameter -l Zeilen } \end{lstlisting} \index{Funktion|)} \subsection{Bedingungen (\texttt{[ ]})}\label{bedingungen}\index{Bedingungen|see{test}}\index{[ ]=\texttt{[ ]}|see{test}}\index{test=\texttt{test}|(textbf} Da die Standard-Shell keine arithmetischen oder logischen Ausdrücke auswerten kann\footnote{\texttt{if} und Konsorten prüfen nur den Rückgabewert\index{Rückgabewert} eines aufgerufenen Programmes~--~0 bedeutet `true', alles andere bedeutet `false', siehe auch \ref{exitcode}.}, muß dazu ein externes Programm benutzt werden. Dieses Programm heißt \verb\test\\index{test=\texttt{test}}. Üblicherweise besteht auf allen Systemen auch noch ein Link namens \verb\[\ auf dieses Programm. Dieser Link ist fast absolut gleichwertig zu benutzen (in dieser Form wird allerdings eine abschließende Klammer nach der Bedingung erwartet). Dementsprechend ist es auch zwingend erforderlich, nach der Klammer ein Leerzeichen zu schreiben. Das dient dazu, Bedingungen in \verb\if\-Abfragen u. ä. lesbarer zu machen. Das \verb\test\-Programm bietet sehr umfangreiche Optionen an. Dazu gehören Dateitests und Vergleiche von Zeichenfolgen oder ganzen Zahlen. Diese Bedingungen können auch durch Verknüpfungen kombiniert werden. \medskip\medskip\emph{Dateitests:}\index{Dateitests}\nopagebreak \LTXtable{\textwidth}{tab_bedingungen_dateitests.tex} \medskip\emph{Bedingungen für Zeichenfolgen:}\nopagebreak \LTXtable{\textwidth}{tab_bedingungen_zeichenfolgen.tex} \medskip\emph{Ganzzahlvergleiche:}\nopagebreak \LTXtable{\textwidth}{tab_bedingungen_ganzzahlvergleiche.tex} \medskip\emph{Kombinierte Formen:}\nopagebreak \LTXtable{\textwidth}{tab_bedingungen_kombinationen.tex} \medskip\emph{Beispiele:}\nopagebreak \LTXtable{\textwidth}{tab_beisp_bedingungen.tex} \index{test=\texttt{test}|)} \subsection{if\ldots}\label{if}\index{if=\texttt{if}|(textbf}\index{then=\texttt{then}|see{if}}\index{elif=\texttt{elif}|see{if}}\index{else=\texttt{else}|see{if}}\index{fi=\texttt{fi}|see{if}} Die \texttt{if}-Anweisung in der Shell-Programmierung macht das gleiche wie in allen anderen Programmiersprachen, sie testet eine Bedingung auf Wahrheit und macht davon den weiteren Ablauf des Programms abhängig. Die Syntax der \texttt{if}-Anweisung lautet wie folgt:\nopagebreak \begin{sybox} \texttt{if }\textsl{Bedingung1} \\ \texttt{then }\textsl{Befehle1} \\ \textsl{[}\texttt{ elif }\textsl{Bedingung2} \\ \hspace*{1em}\texttt{then }\textsl{Befehle2 ]} \\ \hspace*{1em}\texttt{\vdots} \\ \textsl{[}\texttt{ else }\textsl{Befehle3 ]} \\ \texttt{fi} \end{sybox} Wenn die \textsl{Bedingung1} erfüllt ist, werden die \textsl{Befehle1} ausgeführt; andernfalls, wenn die \textsl{Bedingung2} erfüllt ist, werden die \textsl{Befehle2} ausgeführt. Trifft keine Bedingung zu, sollen die \textsl{Befehle3} ausgeführt werden. Bedingungen werden normalerweise mit dem Befehl \texttt{test}\index{test=\texttt{test}} (siehe unter \ref{bedingungen}) formuliert. Es kann aber auch der Rückgabewert\footnote{Siehe unter \ref{exitcode}.}\index{Rückgabewert} jedes anderen Kommandos ausgewertet werden. Für Bedingungen, die auf jeden Fall zutreffen sollen steht der Null-Befehl (\texttt{:}, siehe unter \ref{null-befehl}) zur Verfügung. \medskip\emph{Beispiele:} Man achte auf die Positionierung der Semikola\footnote{Und man verzeihe mir einen eventuell falschen Plural\ldots :-)}.\nopagebreak \begin{lstlisting} # Fuege eine 0 vor Zahlen kleiner 10 ein: if [ $counter -lt 10 ]; then number=0$counter else number=$counter; fi # Loesche ein Verzeichnis, wenn es existiert: if [ -d $dir ]; then rmdir $dir # rmdir: Verzeichnis loeschen fi \end{lstlisting} \index{if=\texttt{if}|)} \subsection{case\ldots}\label{case}\index{case=\texttt{case}|(textbf}\index{esac=\texttt{esac}|see{case}} \index{;;=\texttt{;;}|see{case}} Auch die \texttt{case}-Anweisung ist vergleichbar in vielen anderen Sprachen vorhanden. Sie dient, ähnlich wie die \texttt{if}-Anweisung zur Fallunterscheidung\index{Fallunterscheidung|see{case}}\index{Fallunterscheidung|see{if}}. Allerdings wird hier nicht nur zwischen zwei Fällen unterschieden (Entweder / Oder), sondern es sind mehrere Fälle möglich. Man kann die \texttt{case}-Anweisung auch durch eine geschachtelte \texttt{if}-Anweisung völlig umgehen, allerdings ist sie ein elegantes Mittel um den Code lesbar zu halten. Die Syntax der \texttt{case}-Anweisung lautet wie folgt:\nopagebreak \begin{sybox} \texttt{case }\textsl{Wert}\texttt{ in} \\ \hspace*{1em}\textsl{Muster1}\texttt{) }\textsl{Befehle1}\texttt{;;} \\ \hspace*{1em}\textsl{Muster2}\texttt{) }\textsl{Befehle2}\texttt{;;} \\ \hspace*{1em}\texttt{\vdots} \\ \texttt{esac} \\ \end{sybox} Wenn der \textsl{Wert} mit dem \textsl{Muster1} übereinstimmt, wird die entsprechende Befehlsfolge\index{Befehls>-folge} (\textsl{Befehle1}) ausgeführt, bei Übereinstimmung mit \textsl{Muster2} werden die Kommandos der zweiten Befehlsfolge\index{Befehls>-folge} (\textsl{Befehle2}) ausgeführt, usw. Der letzte Befehl in jeder Gruppe muß mit \texttt{;;} gekennzeichnet werden. Das bedeutet für die Shell soviel wie `springe zum nächsten \texttt{esac}', so daß die anderen Bedingungen nicht mehr überprüft werden. In den Mustern sind die gleichen Meta-Zeichen\index{Meta-Zeichen} erlaubt wie bei der Auswahl von Dateinamen. Das bedeutet, daß man durch ein einfaches \texttt{*}\index{*=\texttt{*}} den Default-Pfad kennzeichnen kann. Dieser wird dann durchlaufen, wenn kein anderes Muster zutrifft. Wenn in einer Zeile mehrere Muster angegeben werden sollen, müssen sie durch ein Pipezeichen (\texttt{|}, logisches ODER) getrennt werden. \medskip\emph{Beispiele:}\nopagebreak \begin{lstlisting} # Mit dem ersten Argument in der Befehlszeile wird die entsprechende # Aktion festgelegt: case $1 in # nimmt das erste Argument Ja|Nein) response=1;; -[tT]) table=TRUE;; *) echo "Unbekannte Option"; exit 1;; esac # Lies die Zeilen von der Standard-Eingabe, bis eine Zeile mit einem # einzelnen Punkt eingegeben wird: while :; do # Null-Befehl (immer wahr) echo "Zum Beenden . eingeben ==> \c" read line # read: Zeile von StdIn einlesen case "$line" in .) echo "Ausgefuehrt" break;; *) echo "$line";; esac done \end{lstlisting} \index{case=\texttt{case}|)} \subsection{for\ldots}\label{for}\index{for=\texttt{for}|(textbf}\index{Schleife>for-=\texttt{for}-|see{for}}\index{in=\texttt{in}|see{for}}\index{in=\texttt{in}|see{case}}\index{do=\texttt{do}|see{for}}\index{do=\texttt{do}|see{while}}\index{do=\texttt{do}|see{until}}\index{do=\texttt{done}|see{for}}\index{do=\texttt{done}|see{while}}\index{do=\texttt{done}|see{until}} Dieses Konstrukt ähnelt nur auf den ersten Blick seinen Pendants aus anderen Programmiersprachen. In anderen Sprachen wird die \texttt{for}-Schleife meistens dazu benutzt, eine Zählvariable über einen bestimmten Wertebereich iterieren zu lassen (\texttt{for i = 1 to 100\ldots next}). In der Shell dagegen wird die Laufvariable nicht mit aufeinanderfolgenden Zahlen belegt, sondern mit einzelnen Werten aus einer anzugebenden Liste\footnote{Wenn man trotzdem eine Laufvariable\index{Laufvariable} braucht, muß man dazu die \texttt{while}-Schleife\index{while=\texttt{while}} `mißbrauchen' (siehe unter \ref{while}).}. Die Syntax der \texttt{for}-Schleife lautet wie folgt:\nopagebreak \begin{sybox} \texttt{for }\textsl{x [}\texttt{ in }\textsl{Liste ]} \\ \texttt{do} \\ \hspace*{1em}\textsl{Befehle} \\ \texttt{done} \end{sybox} Die \textsl{Befehle} werden ausgeführt, wobei der Variablen \textsl{x} nacheinander die Werte aus der \textsl{Liste} zugewiesen werden. Wie man sieht ist die Angabe der \textsl{Liste} optional, wenn sie nicht angegeben wird, nimmt \textsl{x} der Reihe nach alle Werte aus \texttt{\$@} (in dieser vordefinierten Variablen liegen die Aufrufparameter~--~siehe unter \ref{vordefinierte_variablen}) an. Wenn die Ausführung eines Schleifendurchlaufs bzw der ganzen Schleife abgebrochen werden soll, müssen die Kommandos \texttt{continue}\index{continue=\texttt{continue}} (\ref{continue}) bzw. \texttt{break}\index{break=\texttt{break}} (\ref{break}) benutzt werden. \medskip\emph{Beispiele:}\nopagebreak \begin{lstlisting} # Seitenweises Formatieren der Dateien, die auf der Befehlszeile # angegeben wurden, und speichern des jeweiligen Ergebnisses: for file do pr $file > $file.tmp # pr: Formatiert Textdateien done # Durchsuche Kapitel zur Erstellung einer Wortliste (wie fgrep -f): for item in `cat program_list` # cat: Datei ausgeben do echo "Pruefung der Kapitel auf" echo "Referenzen zum Programm $item ..." grep -c "$item.[co]" chap* # grep: nach Muster suchen done # Ermittle einen Ein-Wort-Titel aus jeder Datei und verwende ihn # als neuen Dateinamen: for file do name=`sed -n 's/NAME: //p' $file` # sed: Skriptsprache zur Textformatierung mv $file $name # mv: Datei verschieben bzw. umbenennen done \end{lstlisting} \index{for=\texttt{for}|)} \subsection{while\ldots}\label{while}\index{while=\texttt{while}|(textbf}\index{Schleife>while-=\texttt{while}-|see{while}} Die \texttt{while}-Schleife ist wieder ein Konstrukt, das einem aus vielen anderen Sprachen bekannt ist: die Kopfgesteuerte Schleife. Die Syntax der \texttt{while}-Schleife lautet wie folgt:\nopagebreak \begin{sybox} \texttt{while }\textsl{Bedingung}\texttt{; do} \\ \hspace*{1em}\textsl{Befehle} \\ \texttt{done} \end{sybox} Die \textsl{Befehle} werden so lange ausgeführt, wie die \textsl{Bedingung} erfüllt ist. Dabei wird die \textsl{Bedingung} vor der Ausführung der \textsl{Befehle} überprüft. Die \textsl{Bedingung} wird dabei üblicherweise, genau wie bei der \texttt{if}-Anweisung, mit mit dem Befehl \texttt{test}\index{test=\texttt{test}} (siehe unter \ref{bedingungen}) formuliert. Wenn die Ausführung eines Schleifendurchlaufs bzw der ganzen Schleife abgebrochen werden soll, müssen die Kommandos \texttt{continue}\index{continue=\texttt{continue}} (\ref{continue}) bzw. \texttt{break}\index{break=\texttt{break}} (\ref{break}) benutzt werden. \medskip\emph{Beispiel:}\nopagebreak \begin{lstlisting} # Zeilenweise Ausgabe aller Aufrufparameter: while [ -n "$1" ]; do echo $1 shift # mit shift werden die Parameter nach # Links geshiftet (aus $2 wird $1) done \end{lstlisting} Eine Standard-Anwendung der \texttt{while}-Schleife ist der Ersatz für die Zählschleife\index{Zählschleife}\index{Schleife>Zähl-=Zähl-|see{Zählschleife}}. In anderen Sprachen kann man mit der \texttt{for}-Schleife\index{for=\texttt{for}} eine Laufvariable\index{Laufvariable} über einen bestimmten Wertebereich iterieren lassen (\texttt{for i = 1 to 100...next}). Da das mit der \texttt{for}-Schleife der Shell nicht geht\footnote{Auf einigen Systemen steht für diesen Zweck auch das Kommando \texttt{seq} (Siehe Abschnitt \ref{seq}) zur Verfügung.}, ersetzt man die Funktion durch geschickte Anwendung der \texttt{while}-Schleife:\nopagebreak \begin{lstlisting} # Ausgabe der Zahlen von 1 bis 100: i=1 while [ $i -le 100 ]; do echo $i i=`expr $i + 1` done \end{lstlisting} \index{while=\texttt{while}|)} \subsection{until\ldots}\label{until}\index{until=\texttt{until}|(textbf}\index{Schleife>until-=\texttt{until}-|see{until}} Die \texttt{until}-Schleife ist das Gegenstück zur \texttt{while}-Schleife. Allerdings nicht in dem Sinn, wie sie in den meisten anderen Programmiersprachen verstanden wird. Sie arbeitet in der Shell genau wie die \texttt{while}-Schleife, mit dem Unterschied daß die Bedingung negiert wird. Es ist also auch eine kopfgesteuerte Schleife, die allerdings so lange läuft wie die angegebene Bedingung nicht zutrifft. Die Syntax der \texttt{until}-Schleife lautet wie folgt:\nopagebreak \begin{sybox} \texttt{until }\textsl{Bedingung}\texttt{; do} \\ \hspace*{1em}\textsl{Befehle} \\ \texttt{done} \end{sybox} Die \textsl{Bedingung} wird dabei üblicherweise, genau wie bei der \texttt{if}-Anweisung, mit mit dem Befehl \texttt{test}\index{test=\texttt{test}} (siehe unter \ref{bedingungen}) formuliert. Wenn die Aus\-füh\-rung eines Schleifendurchlaufs bzw der ganzen Schleife abgebrochen werden soll, müssen die Kommandos \texttt{continue}\index{continue=\texttt{continue}} (\ref{continue}) bzw. \texttt{break}\index{break=\texttt{break}} (\ref{break}) benutzt werden. \medskip\emph{Beispiel:} Hier wird die Bedingung nicht per \texttt{test} sondern mit dem Rückgabewert\index{Rückgabewert} des Programms \texttt{grep}\index{grep=\texttt{grep}} formuliert.\nopagebreak \begin{lstlisting} # Warten, bis sich der Administrator einloggt: until who | grep "root"; do # who: Liste der Benutzer # grep: Suchen nach Muster sleep 2 # sleep: warten done echo "Der Meister ist anwesend" \end{lstlisting} \index{until=\texttt{until}|)} \subsection{continue}\label{continue}\index{continue=\texttt{continue}|(textbf} Die Syntax der \texttt{continue}-Anweisung lautet wie folgt:\nopagebreak \begin{sybox} \texttt{continue }\textsl{[ n ]} \end{sybox} Man benutzt \texttt{continue} um die restlichen Befehle in einer Schleife zu überspringen und mit dem nächsten Schleifendurchlauf anzufangen. Wenn der Parameter \textsl{n} angegeben wird, werden \textsl{n} Schleifenebenen übersprungen. \index{continue=\texttt{continue}|)} \subsection{break}\label{break}\index{break=\texttt{break}|(textbf} Die Syntax der \texttt{break}-Anweisung lautet wie folgt:\nopagebreak \begin{sybox} \texttt{break }\textsl{[ n ]} \end{sybox} Mit \texttt{break} kann man die innerste Ebene (bzw. \textsl{n} Schleifenebenen) verlassen ohne den Rest der Schleife auszuführen. \index{break=\texttt{break}|)} \subsection{exit}\label{exit}\index{exit=\texttt{exit}|(textbf} Die Syntax der \texttt{exit}-Anweisung lautet wie folgt:\nopagebreak \begin{sybox} \texttt{exit }\textsl{[ n ]} \end{sybox} Die \texttt{exit}-Anweisung wird benutzt, um ein Skript zu beenden. Wenn der Parameter \textsl{n} angegeben wird, wird er von dem Skript als Exit-Code zurückgegeben. \index{exit=\texttt{exit}|)} \subsection{return}\label{return}\index{return=\texttt{return}|(textbf} Die Syntax der \texttt{return}-Anweisung lautet wie folgt:\nopagebreak \begin{sybox} \texttt{return }\textsl{[ n ]} \end{sybox} Mittels \texttt{return} kann eine Funktion (siehe \ref{funktionen}) einen bestimmten Wert zurückgeben. Anderenfalls wird der Exit-Code des letzten in der Funktion ausgeführten Befehls zurückgegeben. \index{return=\texttt{return}|)} \section{Befehlsformen}\label{befehlsformen}\index{Befehls>-formen|(textbf} \index{\&=\texttt{\&}|(textbf}\index{;=\texttt{;}|(textbf}\index{( )=\texttt{( )}|(textbf}\index{\{ \}=\texttt{\{ \}}|(textbf}\index{Pipe|(textbf}\index{Backticks|(textbf}\index{\&\&=\texttt{\&\&}|(textbf}\index{!|!|=\texttt{!|!|}|(textbf}\index{Befehls>-substitution|(textbf}\index{Befehls>-folge|(textbf}\index{Befehls>-block|(textbf} \index{!|=\texttt{!|}|see{Pipe}}\index{Substitution|see{Befehls-Subst.}} Es gibt eine Reihe verschiedener Möglichkeiten, Kommandos auszuführen:\nopagebreak \LTXtable{\textwidth}{tab_befehlsformen.tex} \medskip\emph{Beispiele:}\nopagebreak \LTXtable{\textwidth}{tab_beisp_befehlsformen.tex} \index{\&=\texttt{\&}|)}\index{;=\texttt{;}|)}\index{( )=\texttt{( )}|)}\index{\{ \}=\texttt{\{ \}}|)}\index{Pipe|)}\index{Backticks|)}\index{\&\&=\texttt{\&\&}|)}\index{!|!|=\texttt{!|!|}|)}\index{Befehls>-substitution|)}\index{Befehls>-folge|)}\index{Befehls>-block|)} \index{Befehls>-formen|)} \section{Datenströme}\label{datenstrom}\index{Datenströme|(textbf} \index{<=\texttt{<}|(textbf}\index{<<=\texttt{<<}|(textbf}\index{!>\&=\texttt{!>\&}|(textbf}\index{!>\&-=\texttt{!>\&-}|(textbf}\index{<\&=\texttt{<\&}|(textbf}\index{<\&-=\texttt{<\&-}|(textbf}\index{!>=\texttt{!>}|(textbf}\index{!>!>=\texttt{!>!>}|(textbf}\index{Pipe|(textbf}\index{Dateideskriptor|(textbf}\index{Standard-Eingabe|(textbf}\index{Standard-Ausgabe|(textbf}\index{Standard-Fehlerausgabe|(textbf}\index{Here-Dokument|(textbf} \index{Deskriptor|see{Dateideskriptor}}\index{Ausgabe|see{Standard-Ausgabe}}\index{Fehlerausgabe|see{Standard-Fehlerausgabe}}\index{Fehlermeldungen|see{Standard-Fehlerausgabe}}\index{stdin|see{Standard-Eingabe}}\index{stdout|see{Standard-Ausgabe}}\index{stderr|see{Standard-Fehlerausgabe}} Eines der markantesten Konzepte, das in Shell-Skripten benutzt wird, ist das der Datenströme. Die meisten der vielen Unix-Tools bieten die Möglichkeit, Eingaben aus der sogenannten Standard-Eingabe entgegenzunehmen und Ausgaben dementsprechend auf der Standard-Ausgabe zu machen. Es gibt noch einen dritten Kanal für Fehlermeldungen, so daß man eine einfache Möglichkeit hat, fehlerhafte Programmdurchläufe zu behandeln indem man die Fehlermeldungen von den restlichen Ausgaben trennt. Es folgt eine Aufstellung der drei Standardkanäle:\nopagebreak \LTXtable{\textwidth}{tab_datenstroeme_kanaele.tex} Die standardmäßige Eingabequelle oder das Ausgabeziel können wie folgt geändert werden: \emph{Einfache Umlenkung:}\nopagebreak\index{Umlenkung} \LTXtable{\textwidth}{tab_datenstroeme_einfach.tex} Die Technik eines Here-Dokuments ist sicherlich auf den ersten Blick etwas verwirrend. Man benutzt Here-Dokumente zum Beispiel in einer Situation, in der ein fest vorgegebener Text benötigt wird. Man stelle sich ein Skript vor, das jeden Tag eine Mail mit festem Inhalt und variablem Anhang verschickt. Oder eine eingebaute Hilfe-Funktion, die bei falschen Parametern einen Hilfetext ausgibt. Natürlich könnte man zu diesem Zweck eine eigene Datei einrichten, aber das ist eigentlich nicht notwendig. Man handelt sich nur Ärger ein, wenn man das Skript auf einen anderen Rechner portiert und die Datei vergißt. Abgesehen davon - wo legt man eine solche Datei sinnvoll ab? Um diesem Ärger zu entgehen, sollte man in einer solchen Situation ein Here-Do\-ku\-ment benutzen. \emph{Umlenkung mit Hilfe von Dateideskriptoren:}\nopagebreak \LTXtable{\textwidth}{tab_datenstroeme_deskriptoren.tex} \emph{Mehrfach-Umlenkung:}\index{Umlenkung}\index{Mehrfach-Umlenkung}\nopagebreak \LTXtable{\textwidth}{tab_datenstroeme_mehrfach.tex} Zwischen den Dateideskriptoren und einem Umlenkungssymbol darf kein Leerzeichen sein; in anderen Fällen sind Leerzeichen erlaubt. \medskip\emph{Beispiele:}\nopagebreak \LTXtable{\textwidth}{tab_beisp_datenstroeme.tex} \medskip\emph{Beispiel eines Here-Dokuments:}\nopagebreak \begin{lstlisting} # Ein Here-Dokument: Nach dem << wird ein sogenannter Delimiter # angegeben. Alle folgenden Zeilen werden an die Standard-Eingabe von # cat übergeben. Der Text wird durch ein erneutes Vorkommen des # Delimiters (einzeln und am Zeilenanfang) beendet. cat << EOF Dieser Text wird zeilenweise ausgegeben, bis ein einzelnes EOF kommt. EOF \end{lstlisting} Gerade der Mechanismus mit dem Piping sollte nicht unterschätzt werden. Er dient nicht nur dazu, relativ kleine Texte zwischen Tools hin- und herzureichen. An dem folgenden Beispiel soll die Mächtigkeit dieses kleinen Zeichens gezeigt werden:\nopagebreak Es ist mit den passenden Tools unter Unix möglich, eine ganze Audio-CD mit zwei Befehlen an der Kommandozeile zu duplizieren. Das erste Kommando veranlaßt, daß die TOC (Table Of Contents) der CD in die Datei cd.toc geschrieben wird. Das dauert nur wenige Sekunden. Die Pipe steckt im zweiten Befehl. Hier wird der eigentliche Inhalt der CD mit dem Tool `cdparanoia' ausgelesen. Da kein Dateiname angegeben wird, schreibt cdparanoia die Daten auf seine Standard-Ausgabe. Diese wird von dem Brennprogramm `cdrdao' übernommen und in Verbindung mit der TOC `on the fly' auf die CD geschrieben.\label{cdrdao}\nopagebreak \begin{lstlisting} cdrdao read-toc --datafile - cd.toc cdparanoia -q -R 1- - | cdrdao write --buffers 64 cd.toc \end{lstlisting} \index{<=\texttt{<}|)}\index{!>\&=\texttt{!>\&}|)}\index{!>\&-=\texttt{!>\&-}|)}\index{<\&=\texttt{<\&}|)}\index{<\&-=\texttt{<\&-}|)}\index{!>=\texttt{!>}|)}\index{!>!>=\texttt{!>!>}|)}\index{Pipe|)}\index{Dateideskriptor|)}\index{Standard-Eingabe|)}\index{Standard-Ausgabe|)}\index{Standard-Fehlerausgabe|)}\index{Here-Dokument|)textbf} \index{Datenströme|)}