10 Die UNIX-Shell

10.1 Einleitung

Bereits in der Grundeinführung am Anfang des Heftes haben wir die ksh als Kommandointerpretierer kennengelernt. Neben der Eigenschaft, Eingaben des Benutzers in Programmaufrufe umzusetzen, kann die Shell auch Programme ausführen, die in einer speziellen Kommandosprache geschrieben wurden. Diese Programme nennt man Shell-Scripts. Shell-Scripts können immer wiederkehrende Arbeitsabläufe automatisieren und vereinfachen. Dieses Kapitel befaßt sich mit der Erstellung von Shell-Scripts und der Verwendung von Shell-Variablen und Umgebungsvariablen.

10.2 Ein- und Ausgabe

Im Normalfall werden Daten über die Tastatur ein- und am Bildschirm ausgegeben. Man bezeichnet daher die Tastatur als Standardeingabe und den Bildschirm als Standardausgabe. Die UNIX-Shells bieten jedoch syntaktische Konstruktionen, mit deren Hilfe man die Ein- oder Ausgabe eines Programms in eine Datei oder in ein anderes Programm umleiten kann. Einige UNIX-Programme wurden speziell für die Verwendung im Rahmen dieser Ein- und Ausgabeumlenkung entwickelt. Mit deren Hilfe lassen sich viele auf den ersten Blick komplizierte Aufgaben einfach durch Verketten verschiedener Programme zu einem Befehlsstrom lösen.

Die Multitasking-Fähigkeiten von UNIX sowie das im Vergleich zu DOS wesentlich effektivere Dateisystem sorgen dafür, daß die hier angegebenen Konstruktionen auch in der täglichen Arbeit ein effektives und schnelles Hilfsmittel darstellen. Im Gegensatz zu DOS wird nämlich unter UNIX nicht immer die ganze Ausgabe eines Programms auf dem Datenträger gespeichert, bevor das nächste Programm startet, sondern es wird ständig zwischen den verschiedenen Programmen umgeschaltet und die anfallende Datenmenge dadurch gering gehalten. Auf diese Art müssen die Daten im Regelfall gar nicht auf den Datenträger geschrieben werden, sondern werden über im Hauptspeicher vorhandene Puffer weitergegeben.

10.2.1 Die Pipeline

Syntax:
Befehl1 | Befehl2 | Befehl3 ...
Beschreibung:
 Die Ausgabe von Befehl1 wird direkt als Eingabe für Befehl2 verwendet usw. Die Ausführung erfolgt gleichzeitig, die Daten wandern gewissermaßen von links nach rechts.
Beispiel:
ls -l | more
Das ausführliche Inhaltsverzeichnis des aktuellen Verzeichnisses wird seitenweise angezeigt.

10.2.2 Eingabe von einer Datei holen

Syntax:
Befehl <Datei
Beschreibung:
 Die Eingabe, die der Befehl erwartet und die normalerweise über die Tastatur erfolgt wäre, wird statt dessen aus der angegebenen Datei geholt. Oftmals bedingt dies, daß der Befehl beendet wird, sobald die gesamte Datei verarbeitet worden ist. Ein denkbarer Einsatz dieser Methode ist z.B. die Speicherung von Kommandos für einen Editor, wenn oftmals die gleichen Umformungen einer Datei notwendig sind und man die Befehle nicht immer per Hand eingeben will.

10.2.3 Das here-document

Syntax:
Befehl <<Endemarkierung
Beschreibung:
 Die Eingabe zum Befehl wird direkt aus den folgenden Eingabezeilen (oder in einer Shellprozedur, wo man dies normalerweise anwenden, aus den nächsten Zeilen der selben Datei) entnommen und als Standardeingabe dem Befehl zur Verfügung gestellt. Eine Zeile, die genau die Endemarkierung enthält, deutet an, daß die Eingabe zu Ende ist. Die folgende Zeile wird also wieder als Befehlszeile für die Shell aufgefaßt.
Hinweis:
 Normalerweise werden die Eingabezeilen den Ersetzungsregeln der Shell unterworfen, bevor sie an Befehl weitergeleitet werden. Wenn aber Endemarkierung in Hochkommas eingefaßt wird, wird die Eingabe unverändert weitergegeben.
 

10.2.4 Ausgabe umlenken

10.2.4.1 Ausgabe in eine Datei umleiten

Syntax:
Befehl >Datei
Beschreibung:
 Die Ausgabe wird in die Datei Datei gelenkt, es erfolgt keine Ausgabe auf dem Bildschirm. Wenn Datei noch nicht existiert, so wird sie erzeugt. Ein bereits vorhandener Dateiinhalt wird ohne Warnung überschrieben (in der ksh kann das durch set noclobber verhindert werden). Will man die alte Datei belassen und lediglich etwas anfügen, so benützt man:
Befehl >>Datei
Auch diese Variante erzeugt die Datei, falls sie noch nicht existiert.
Beispiel:
ls -l >datlist
erzeugt eine Datei namens datlist, die das ausführliche Inhaltsverzeichnis des aktuellen Verzeichnisses enthält.

10.2.4.2 Fehlerausgabe umlenken

Syntax:
Befehl 2>Datei
Beschreibung:
 Fehlermeldungen, die bei der Ausführung von Befehl aufgetreten sind, werden in der Datei gespeichert. Dies ist beispielsweise sinnvoll, wenn die ersten (und meistens wichtigsten) Fehlermeldungen eines Compilerlaufs immer aus dem Bildschirm herausrollen.

10.2.5 Ausgabe als Befehlseingabe verwenden

Syntax:
Befehl1 ... `Befehl2` ...
Beschreibung:
 Zunächst wird Befehl2 ausgeführt. Seine Ausgabe wird in der Kommandozeile an die Stelle seines Aufrufes einkopiert. Anschließend wird Befehl1 mit der modifizierten Kommandozeile ausgeführt.
Beispiel:
eval `resize`
Dieses Kommando wird insbesondere verwendet, wenn sich die Größe eines Terminalfensters geändert hat. Es paßt die Umgebungsvariablen der Shell an die neue Fenstergröße an. resize erzeugt dabei die nötigen Kommandos, die anschließend durch eval ausgewertet werden. (Für Interessierte: echo `resize` zeigt die erzeugten Kommandos an.)
 

10.3 Verhinderung von Shell-Interpretationen

In der Grundeinführung haben wir gesehen, daß die Shell gewisse Zeichenfolgen in der Kommandozeile interpretiert und entweder durch andere Zeichenfolgen ersetzt oder auf Grund dieser Zeichenfolgen gewisse Aktionen ausführt (Ein-/Ausgabeumlenkung, Hintergrundprozesse, ... ). Dieses Verhalten der Shell ist manchmal unerwünscht, zum Beispiel, wenn einmal wirklich ein * in der Kommandozeile stehen soll. Man kann daher auf mehrere Arten verhindern, daß die Shell gewisse Ersetzungen durchführt.

Dieses Verhindern von Interpretationen bestimmter Zeichen wird auch als Quoting bezeichnet.

Alle in den nachfolgenden Abschnitten erklärten Quoting-Mechanismen lassen sich gefahrlos mit Hilfe des Befehls echo testen.

10.3.1 Der Backslash (\)

Der Backslash (\) macht die Sonderbedeutung des nachfolgenden Zeichens unwirksam.

echo \*

erzeugt zum Beispiel die Ausgabe

*

Eine Besonderheit hierbei ist die Eingabe von \ mit nachfolgendem Drücken der ENTER-Taste. Der dadurch am Bildschirm sichtbare Zeilenvorschub wird bei der Interpretation der Kommandozeile komplett ignoriert. Auf diese Art ist es möglich, lange Befehle in mehreren aufeinanderfolgenden Zeilen einzugeben, um die Übersichtlichkeit zu verbessern. Ruft man die so eingegebenen Zeilen mit Hilfe der History-Funktionen der Unix-Shell noch einmal auf, so werden die Zeilenumbrüche in der Form ^J dargestellt. ^J ist dabei ein einzelnes Zeichen (Ctrl-J), was man auch beim Bewegen des Cursors auf der Zeile feststellen kann.

10.3.2 Das Hochkomma (')

Will man mehrere Zeichen oder Worte vor einer Interpretation schützen, so ist das Verfahren mit vorangestelltem Backslash etwas unpraktikabel. Für diesen Fall kann man die entsprechende Zeichenkette in zwei Hochkommata einschließen. Die ist insbesondere dann sinnvoll, wenn mehrere durch Zwischenräume getrennte Worte als ein einziges Argument einem Programm übergeben werden sollen. Ohne den Einschluß in Hochkommata würde die Shell in diesem Fall Worttrenner erkennen und dementsprechend mehrere Einzelparameter erzeugen. Innerhalb einer mit Hochkommata versehenen Zeichenkette darf dann allerdings kein weiteres Hochkomma stehen, auch nicht als \'.

Es ist möglich, nicht gequotete und gequotete Zeichenketten zu einem Parameter zusammenzufügen. Dies geschieht einfach durch hintereinanderschreiben der entsprechenden Ausdrücke.

Beispiel:
 Die folgende Sequenz verdeutlicht die Wirkung des Quoting. Der Shell-Prompt besteht in diesem Beispiel nur aus dem Dollarzeichen.

$c=test
$ echo $c
test
$ echo '$c'
$c
$ echo $c'$c'
test$c

Erklärung:

10.3.3 Das Anführungszeichen (")

Das Anführungszeichen läßt im Gegensatz zum Hochkomma Ersetzungen von Shell-Variablen und die Interpretation des Backquote-Konstruktes zu, verbietet aber die Interpretation von Dateinamen-Kurzschreibweisen (*, ? ... ). Dies macht man sich am besten klar, wenn man das folgende Kommando eingibt:

echo "* `ls`"

Das Ergebnis zeigt nach dem *, der augenscheinlich nicht interpretiert wird, die Liste von Dateinamen, die ls erzeugt.

Innerhalb einer von Anführungszeichen eingeschlossenen Zeichenkette ist des weiteren das Quoting mit Hilfe des Backslash erlaubt. Insbesondere darf auch ein in der Zeichenkette vorkommendes Anführungszeichen durch einen Backslash gequotet werden, so daß die Shell es nicht als Ende der Zeichenkette erkennt.

Hinweis: Durch Hochkomma oder Anführungszeichen begrenzte Strings dürfen sich über mehrere Zeilen erstrecken. Wenn also überraschend ein Prompt > auftritt, dann solltet Ihr überprüfen, ob Ihr irgendwo das schließende Anführungszeichen vergessen habt.

10.4 Variablen der Shell

Die Unix-Shell bietet die Möglichkeit, Variablen zu definieren und ihnen bestimmte Werte zuzuweisen. Die Namen der Variablen sind frei wählbar, jedoch sind bestimmte Variablen von der Shell mit einer Sonderbedeutung versehen, daher sollte man für selbstdeklarierte Variablen nur Namen verwenden, die mit einem Buchstaben beginnen.

Um eine Variable zu deklarieren, weist man Ihr einen Wert zu. Beispiel:

versuch='Hallo, Welt'

Die Hochkommata verhindern, daß die Shell an der Zeichenkette irgendwelche Interpretationen vornimmt.

Anschließend kann man die Belegung von versuch mit dem Kommando echo $versuch abfragen. Das vorangestellte $-Zeichen sagt der Shell, daß die folgende Zeichenkette der Name einer Variablen oder einer Umgebungsvariablen ist.

Eine Liste aller derzeit gesetzten Shellvariablen erhält man durch den Befehl set.

10.4.1 Shell-Variable und Umgebungsvariable

Neben den internen Variablen der Shell gibt es die sogenannten Environment-Variablen oder Umgebungsvariablen. Diese Variablen sind im Gegensatz zu den Shell-Variablen auch allen Programmen zugänglich, die von der Shell gestartet werden, und können von diesen abgefragt werden.

Zur Anzeige aller gesetzten Umgebungs-Variablen verwendet man den Befehl env. Mit dem Befehl export erklärt man eine Shell-Variable zur Umgebungsvariable. Nachfolgende Änderungen an der Shellvariable ändern dann automatisch auch die Umgebungsvariable mit.

Hinweis: Eine Umgebungsvariable wird immer nur auf nachfolgend gestartete Programme vererbt. Bereits laufende Programme erfahren keine Änderung, da bei jedem Programmstart für dieses eine Kopie der Umgebung angelegt wird. Änderungen werden auch niemals an den Vaterprozeß weitergegeben, das ist nicht möglich.

10.4.2 Bereits vom System festgelegte Variablen

Einige wichtige benutzerspezifische Daten werden bereits beim Anmelden vom System belegt. Diese sind zum Teil von der benutzten Shell abhängig. Die wichtigsten Variablen bei der K-Shell sind (Großschreibung beachten):

HOME
 enthält den Namen des Verzeichnisses, in dem sich der Benutzer nach dem Anmelden befindet und in das er durch die Eingabe von cd gelangt.
PATH
 enthält eine Liste der Verzeichnisse, die bei einem Kommandoaufruf nach dem gewünschten Programm durchsucht werden. Die einzelnen Verzeichnisse sind durch einen : getrennt.
PS1
 enthält das Promptzeichen (Eingabeaufforderungszeichen) der Shell. ksh erlaubt auch die Angabe von Variablen im Prompt; diese werden dann jeweils bei der Anzeige des Prompts ersetzt. (Wenn Ihr PS1 mit Variablen setzt, müßt Ihr diese in Hochkommas setzen, weil sie sonst schon beim Setzen ersetzt werden und dann veralten. Das aktuelle Verzeichnis steht zum Beispiel in PWD.
?
 Enthält den Return-Code des letzten ausgeführten Kommandos. Die meisten Kommandos geben nach erfolgreicher Ausführung den Wert 0 zurück, bei Fehlern einen Wert ungleich 0. Dieser Wert ist normalerweise dem Benutzer verborgen, kann aber bei der Programmierung von Shell-Scripts (siehe unten) sehr nützlich sein.
$
 enthält die Prozeßnummer der aktuellen Shell.
TERM
 der Typ des verwendeten Terminals. Vor allem Programme, die seitenorientiert arbeiten, müssen wissen, welches Terminal benützt wird, um z.B. den Cursor an eine bestimmte Position setzen zu können.

Auch diese voreingestellten Variablen lassen sich ändern; Änderungen können aber, wenn sie unbedacht vorgenommen werden, recht ärgerliche Wirkungen haben.

Wenn Ihr beispielsweise den PATH ersetzt, so findet Ihr anschließend viele Kommandos nicht mehr. So könnt Ihr den Pfad ergänzen:
PATH=$PATH:neuesverz.

10.5 Shell-Prozeduren

Kommandofolgen, die öfter benötigt werden, können in Shellprozeduren zusammengefaßt werden. Im einfachsten Fall sind Shellprozeduren die sequentielle Verkettung von Befehlen, die andernfalls in gleicher Reihenfolge interaktiv vom Benutzer gegeben würden; aber auch Schleifenkonzepte und Unterprogrammstrukturen sind möglich.

10.5.1 Aufruf einer Shellprozedur

Eine Kommandofolge sei in der Datei file gespeichert. Dann kann diese Shell-Prozedur auf drei Arten aufgerufen werden:

10.5.2 Erstellung von Shellprozeduren

Mit Hilfe eines Editors werden die Shellkommandos in eine Datei geschrieben. Nach dem Editieren sind die Prozeduren, falls sie inhaltlich und syntaktisch korrekt sind, ablaufbereit. Wie erwähnt interpretiert die Shell die Kommandos, d. h. einen Compiler o. ä. gibt es nicht.

Kommentare werden in einem Shell-Script durch das Nummernzeichen (#) eingeleitet. Der gesamte Inhalt einer Zeile ab dem Nummernzeichen wird dann von der Shell nicht beachtet. Kommentare können an jeder beliebigen Stelle in einem Shell-Script stehen. Eine Besonderheit bildet jedoch ein Kommentar in der allerersten Zeile der Scriptdatei.

Da es auf dem System unterschiedliche Shells mit zum Teil auch unterschiedlicher Kommandosyntax gibt (insbesondere, was Schleifen oder Abfragen betrifft), kann man in einem Shellscript festlegen, welches Programm zur Interpretation dieses Scripts eingesetzt werden soll. Dazu muß in die erste Zeile des Shellscripts die Zeichenfolge #!, gefolgt vom vollständigen Pfadnamen des gewünschten Programms stehen. Dies bewirkt intern, daß eine Kommandozeile generiert wird, in der das so angegebene Kommando steht, gefolgt vom Dateinamen des Shellscripts. Um zum Beispiel das Skript myprog von der Unix-Shell ausführen zu lassen, muß dessen erste Zeile so lauten:

#!/bin/ksh

Der Aufruf, der dann letztendlich erzeugt wird, wenn man in der Kommandozeile myprog angibt, lautet:

/bin/ksh myprog

Man kann in die erste Zeile des Shellscripts nach dem Programmnamen auch Aufrufoptionen für das entsprechende Programm angeben (jedoch nur ein Wort!). Will man zum Beispiel erreichen, daß die ksh ausgeführten Befehl auf der Standardausgabe anzeigt, schreibt man in die erste Zeile des Scripts:

#!/bin/ksh -x

Dies erweist sich bei der Fehlersuche in Shellscripts als sehr nützlich. Noch ausführlichere Informationen erzeugt der Aufruf

#!/bin/ksh -xv

Fehlt eine Programmangabe in der ersten Zeile des Scripts, so wird zur Interpretation die Datei /bin/sh verwendet. /bin/sh ist die ursprünglichste UNIX-Shell. Sie wird heute fast nur noch zur Interpretation von Scripts eingesetzt, da sie wenig Komfort bietet, aber auf jedem UNIX-System vorhanden ist.

10.5.3 Shell-Prozeduren und Parameter

Shell-Prozeduren können beim Aufruf Parameter mitgegeben werden; auf diese kann in der Prozedur mit $1, $2, ... (je nach Position) zugegriffen werden. Die Variable $0 ist mit dem Namen der Prozedur vorbelegt. Werden einer Prozedur mehr als 9 Parameter zugewiesen, so sind die Parameter ab Position 10 mit ${10}, ${11} ... anzusprechen. Die Variable $* steht für alle übergebenen Parameter. Die Anzahl der übergebenen Parameter steht in der Variablen $#.

Der Befehl shift verschiebt die Parameterliste um einen Wert nach links.
 

Beispiel:
 Sei $1=xx, $2=xy, $3=yy ... $9=bb ${10}=ff.
Dann gilt nach dem Befehl shift folgende Belegung:
$1=xy, $2=yy, ... $8=bb, $9=ff, ${10}=(leer).


Ein Verschieben der Parameter nach rechts ist nicht möglich.

10.5.4 Bedingungen, Mustererkennung und Schleifen

Ihre Leistungsfähigkeit verdanken die Shell-Prozeduren zum großen Teil der Möglichkeit, ihren Ablauf mittels verschiedener Kontrollstrukturen zu steuern. Diese entsprechen in ihrer Arbeitsweise in etwa jenen, wie sie auch in den meisten prozeduralen Programmiersprachen vorhanden sind.

Natürlich können die folgenden Befehle auch interaktiv eingegeben werden, das wird aber abgesehen von for und if zu Testzwecken eher selten gemacht.

10.5.4.1 Die bedingte Anweisung

Syntax:
if Kommandofolge
then Kommandofolge
[elif Kommandofolge]
...
[else Kommandofolge]
fi
Beschreibung:
Zunächst wird die hinter if stehende Kommandofolge ausgeführt. In Abhängigkeit davon, welchen Returncode diese ergibt, wird einer der folgenden Zweige zur Verarbeitung ausgewählt. Ein Returncode von 0 (kein Fehler) wählt den then-Zweig aus, jeder andere den else-Zweig. elif ist für geschachtelte Abfragen nützlich, da man sonst am Ende eine Reihe von fis schreiben müßte.

10.5.4.2 Mehrfachverzweigung durch Mustervergleich

Syntax:
case Zeichenkette in
   Muster) Kommandofolge ;;
   Muster) Kommandofolge ;;
   ...
   Muster) Kommandofolge ;;
esac

Ausgeführt wird die erste Kommandofolge, deren Musterangabe zu der im case-Ausdruck angegebenen Zeichenkette paßt. In der Musterangabe sind alle bereits für Dateinamen beschriebenen Formen der Kurzschreibweise verwendbar.

Anmerkung:
 Es gibt keinen expliziten else-Fall (d.h. falls gar kein Muster paßt), man kann ihn sich jedoch leicht konstruieren, da * eine beliebige Zeichenkette substituieren kann. Es gibt keine Überprüfung, ob die Vergleichsmuster untereinander verschieden sind. Falls mehrere Muster zur Zeichenkette passen, wird nur die Kommandofolge nach dem ersten erkannten Muster durchgeführt, die anderen werden nicht ausgeführt.


Wird als Muster eine Variable verwendet, die leer sein kann, so sollte sie in "" eingefaßt werdedn, um einen Syntaxfehler (kein Wort zwischen case und in) zu vermeiden.

10.5.4.3 Schleife mit Schleifenzähler

Syntax:
for Variable [in Argumentliste]
do
   Kommandofolge
done
Anmerkung:


Der Variablen werden der Reihe nach alle angegebenen Argumente zugewiesen. Fehlt die Angabe einer Liste, so werden statt dessen die Aufrufparameter der Prozedur verwendet.

Eine evtl. angegebene Liste muß auf der selben Zeile stehen wie das Wort for. Wenn sie jedoch zu lang ist, kann man einen Zeilenumbruch verstecken (Backslash, siehe 10.3.1)

10.5.4.4 while-Schleife

Syntax:
while Kommandofolge
do
   Kommandofolge
done
Beschreibung:
 Solange die Kommandofolge (nach while) den Returncode 0 liefert (erfolgreiche Ausführung der Kommandos), wird die Schleife ausgeführt.

10.5.4.5 until-Schleife

Syntax:
until Kommandofolge
do
   Kommandofolge
done
Beschreibung:
 Die until-Schleife verhält sich wie eine while-Schleife mit dem Unterschied, daß die Schleifenbedingung einen Returncode ungleich 0 liefern muß, damit die Schleife durchlaufen wird.

10.5.5 Beispiel für eine Shell-Prozedur

Die folgenden beiden Shell-Prozeduren haben die Aufgabe, im aktuellen Verzeichnis nach Dateien zu suchen, die mit den Zeichen .f (Zeichen für Fortran-Programme) enden.

Die erste Prozedur:

#Prozedur filecheck
echo "$0 gestartet"
params=`ls` # der Variablen params wird das Inhaltsverzeichnis des
 # aktuellen Directories zugewiesen
echo $params
sh for_detecter $params #Aufruf der Shell-Prozedur for_detecter
echo "das wars"
Die zweite Prozedur:
#Prozedur for_detecter (wird von filecheck aufgerufen)
echo "$*"
while [ $# -ne 0 ] # solange die Anzahl der Parameter >0
do
 echo "$1"
 case "$1" in
 *.f) echo "Oh weh, ein Fortran-Programm";; #Extension .f
 *) echo "gut so";; #andere Dateinamen
 esac
 shift #Schiebe die Parameterliste nach links
done
echo "Ende der Unterprozedur"

Das Kommando sh filecheck führt zu folgender Ausgabe:

filecheck gestartet
aligatormaximus.f filecheck for_detecter giraffe.c never.f no_name.c
aligatormaximus.f filecheck for_detecter giraffe.c never.f no_name.c
aligatormaximus.f
Oh weh, ein Fortran-Programm
filecheck
gut so
for_detecter
gut so
giraffe.c
gut so
never.f
Oh weh, ein Fortran-Programm
no_name.c
gut so
Ende der Unterprozedur
das wars