Shell-Programmierung/wie_sieht_ein_shell_skript_aus.tex

1100 lines
55 KiB
TeX

% $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\-\-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\-\-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 wurde mit dem Aufkommen modernerer 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. Interpretiert wird die Zeile vom Kernel,
in der Shell selbst 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 wirkt ähnlich wie ein \lstinline|#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. \lstinline|.profile|,
\lstinline|.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}
Ursprünglich konnte die Standard-Shell keine arithmetischen oder logischen
Ausdrücke auswerten\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}.}. Für diese
Aufgabe mußte ein externes Programm benutzt werden, heutzutage ist der Befehl
in die Shell integriert.
Dieser Befehl heißt \verb\test\\index{test=\texttt{test}}. Üblicherweise
steht er auf allen Systemen auch noch unter dem Namen \verb\[\ zur Verfügung.
Diese Variante 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.
\verb\test\ 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}
Ein weiterer typischer Anwendungsfall ist das zeilenweise Bearbeiten einer
Eingabedatei. Dabei kann es sich entweder um eine einfache Textdatei handeln,
oder um die Ausgabe eines anderen Kommandos.
Um die Ausgabe eines anderen Kommandos zu verarbeiten kann \texttt{while} als
Teil einer Pipeline geschrieben werden:
\begin{lstlisting}
# "hallo" suchen und umstaendlich ausgeben:
grep "hallo" datei.txt | while read zeile; do
echo "Fundstelle: $zeile"
done
\end{lstlisting}
Wenn die Eingabe als Textdatei vorliegt ist es verlockend, diese einfach
mittels \texttt{cat} auszugeben und per Pipe in die Schleife zu schicken.
Allerdings sollte an dieser Stelle eine Umleitung benutzt werden. So vermeidet
man den überflüssigen Start des Kommandos \texttt{cat}:
\begin{lstlisting}
# Zahlen aus einer Datei lesen und aufsummieren:
summe=0
while read zeile; do
summe=`expr $summe + $zeile`
done < datei.txt
echo "Summe: $summe"
\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. So
kommen Verkettungen, Abhängigkeiten und Gruppierungen zustande:\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|)}