Shell und Shellscripting

Allgemeines

Die Shell ist das primäre Werkzeug mit dem unter Linux/Unix Kommandos eingegeben werden. Die Shell ist jedoch mehr als nur ein Eingabeprogramm für Kommandos. Sie bietet einige Möglichkeiten sie komfortabler anzupassen und kann auch als einfache Scriptsprache dienen.

Arten von Shells

Die erste ursprüngliche Unixshell war die Bourne-Shell, benannt nach ihrem Programmierer. Diese Shell war noch sehr rudimentär. Ihr fehlten noch die meisten der Features moderner Shells. Aufgrund der Schwächen der Bourne-Shell wurde in Anlehnung an die Programmiersprache C die C-Shell kurz csh genannt entwickelt. Sie erweiterte die Fähigkeiten der Shell durch Jobkontrolle, Aliase, eine History und die Vervollständigung von Dateinamen. Allerdings war die Syntax der csh inkompatibel mit der Syntax der Bourne-Shell, so das es von da an grundsätzlich 2 Arten von Shells gab: C-Shell kompatible Shells und Bourne-Shell kompatible Shells.

Die Korn-Shell kurz ksh ist eine Weiterentwicklung der Bourne-Shell, die diese um viele Funktionen die auch die csh kennt ergänzt und noch einige weitere Funktionen hinzufügte. Die ksh ist heute noch relativ verbreitet und existiert in mehreren Versionen: Als ksh88, dies ist die ursprüngliche Version der ksh, als ksh93 eine weiterentwickelte Variante der ksh88 und als pdksh, was für Public Domain ksh steht. Die pdksh wurde als OpenSource Variante entwickelt, da die ursprünglichen Versionen der ksh keine OpenSource-Software waren. Inzwischen ist auch der Source-Code der ksh93 freigegeben worden.

Das Gnu-Projekt, das sich zum Ziel gesetzt hat ein Unixkompatibles vollständig freies Betriebssystem zu programmieren, benötigte für dieses Vorhaben natürlich auch eine Shell. Die Shell des GNU-Projektes ist die bash, ein Wortspiel aus Bourne Again Shell. Die bash verfügt über viele Funktionen der ksh und auch noch über einige weitere Merkmale, die sie zu einer sehr modernen komfortablen Shell machen. Die bash wird heutzutage auf den meisten Linuxsystemen als Standardshell verwendet.

Die zsh ist eine sehr moderne und komfortable Shell die Dinge wie etwa automatische Kompletierungsfunktionen auf die Spitze treibt. Die zsh versucht so komfortabel wie möglich zu sein und wird mit hunderten von vorgefertigten Kompletierungsmustern für Kommandos ausgeliefert. Sie ist zwar auch extrem anpassbar, verlangt aber auch für solche Anpassungen mehr Einarbeitung und Hintergrundwissen. Die programmierbare Kompletierungsfunktion ist sehr mächtig aber auch komplex.

Die tcsh ist im Gegensatz zu den anderen vorgestellten Shells keine Bourne-Shell kompatible Shell, sondern eine Weiterentwicklung der csh. Die tcsh ergänzt die csh um eine komfortablere History, Kommandozeileneditierung und eine programmierbare Kompletierungsfunktion.

Daneben gibt es noch einige weitere Shells mit teilweise recht spezieller Ausrichtung wie etwa der Almquist Shell ash, welche versucht besonders klein und ressourcenfreundlich zu sein, dafür aber auch auf die Komfortfunktionen der anderen Shells verzichtet.

Im weiteren Verlauf dieser Unterlagen, werde ich insbesondere auf die unter Linux übliche bash eingehen, wobei das meiste Gesagte auch für andere Bourne-Shell kompatible Shells wie etwa die ksh gilt.

Interaktive Verwendung

Dateinamen- und Pfadvervollständigung

Häufig muß man zu einem Befehl einen Dateinamen oder einen Pfad mit eintippen. In diesem Fall kann einem die Shell weiterhelfen: Hat man einen Teil des Namens geschrieben und gibt es keine Doppeldeutigkeiten, kann die Shell den Namen mit einem Druck auf die Tabtaste vervollständigen. Sollte der Name noch nicht eindeutig sein, versucht ihn die Shell soweit es möglich ist zu vervollständigen und gibt dann einen Piepton aus.

History

Häufig will man Befehle wiederholen, bzw. einen ähnlichen Befehl wie einen bereits vorher abgesetzen ausführen. Um dies zu vereinfachen gibt es eine Historyfunktion in der Shell. Über die History kann man jeden vorher abgesetzen Befehl einfach nochmal in die Kommandozeile laden. Den vorher verwendeten Befehl erhält man durch drücken der Pfeil nach oben Taste. Durch mehrmaliges drücken der Pfeil nach oben Taste, erhält man jeweils noch ältere Befehle. Den wieder in die aktuelle Komandozeile geladenen Befehl kann man noch abändern und dann durch Enter wieder ausführen.

Damit die History auch nach einem Logout und erneuten Einloggen noch alte Befehle bereit hält, wird die History bei der bash in die Datei ~/.bash_history gespeichert.

Globbing

Als Globbing wird die Fähigkeit der Shell bezeichnet mit Wildcardzeichen umgehen zu können. Möchte man eine bestimmte Aktion etwa auf alle Dateien im aktuellen Verzeichnis anwenden, die mit .txt enden, kann man anstatt alle Dateien einzeln aufzuführen auch folgendes Konstrukt verwenden: *.txt. Existieren in einem Verzeichnis die 4 Dateien: datei, datei2.gz, datei3.txt und text.txt, dann würde ein rm *.txt durch die Shell in rm datei3.txt text.txt aufgelöst werden, wodurch also diese beiden Dateien gelöscht werden. Ein rm * löscht alle Dateien im aktuellen Verzeichnis. Mit einem rm -r * sollte man also sehr vorsichtig umgehen. Neben dem * für beliebige Zeichen erkennt die Shell auch noch ? für ein einzelnes beliebiges Zeichen.

Profildateien

Beim starten liest die bash sogenannte Profildateien aus. Welche das sind, hängt davon ab, ob die Shell als Login-Shell oder als interaktive Shell gestartet wurde. Eine Login-Shell ist die Shell die beim direkten anmelden an den Rechner gestartet wird. Hat man sich jedoch bereits angemeldet und startet eine zusätzliche Shell um Kommandos einzugeben, ist dies eine interaktive Shell. Hat man sich bereits grafisch eingeloggt, wird keine Login-Shell geöffnet. Öffnet man dann z. B. ein Xterm ist die darin gestartete Shell eine interaktive Shell. Allerdings kann man dies auch umkonfigurieren, so das Xterms Login-Shells starten, was aber nicht das Standardverhalten ist.

Eine bash die als Login-Shell gestartet wird sucht nach folgenden Dateien: Zuerst nach /etc/profile. Wird diese Datei gefunden, wird ihr Inhalt ausgeführt. Dann nach den Dateien ~/.bash_profile, ~/.bash_login, ~/.profile in dieser Reihenfolge. Die erste dieser 3 Dateien die gefunden wird, wird ausgeführt. Wird eine Datei gefunden werden die weiteren nicht mehr benutzt. Ist also eine ~/.bash_profile vorhanden, wird die ~/.profile nicht ausgeführt. Dies ist praktisch, da ~/.profile auch von beispielsweise der ksh als Logindatei benutzt wird. Möchte man für beide Shells gemeinsame Startdateien kann man ~/.profile nutzen, ansonsten ~/.bash_profile. Beim Logout liest die Shell außerdem ~/.bash_logout und führt den Inhalt aus.

Eine Shell die jedoch keine Loginshell ist, liest diese Dateien nicht. Solch eine Shell liest stattdessen die Datei ~/.bashrc. Häufig möchte man das es eine Datei gibt die von allen Shells gelesen wird. Daher ist es üblich alles was in diese Datei gehört in ~/.bashrc zu schreiben und in die ~/.bash_profile folgende Zeile aufzunehmen, welche dafür sorgt das die ~/.bashrc auch von Loginshells gelesen wird:

test -r ~/.bashrc && . ~/.bashrc

Dieser Befehl testet zunächst ob die Datei ~/.bashrc existiert und lesbar ist, ist dies der Fall wird die Datei eingelesen und ausgeführt. Der Punkt "." nach den && bedeutet das die Datei die dahinter steht wie ein Teil der momentanen Datei ausgeführt werden soll. Auf die Syntax des Befehls "test" werde ich noch später beim Shellscripting eingehen.

Das Tilde-Zeichen "~" steht übrigens immer für das Heimatverzeichnis des jeweils aktuellen Benutzers. Der führende Punkt bei Dateinamen bedeutet, daß diese Datei eine versteckte Datei ist. Unter Unix sind alle Dateien deren Namen mit "." beginnt versteckte Dateien.

In der ksh wird als Profildatei für Loginshells die Datei ~/.profile gelesen. Eine Shell die keine Loginshell ist, liest in der ksh per default keine Startdatei. Dieses Verhalten läßt sich aber ändern, indem man die Umgebungsvariable ENV setzt (z. B. in der ~/.profile oder bei grafischen Logins in einer anderen Datei die beim Login ausgelesen wird):

export ENV='~/.kshrc'

Diese Zeile bewirkt das eine ksh die Datei ~/.kshrc immer auch als zusätzliche Startdatei verwendet, egal ob es sich um eine Loginshell handelt oder nicht. Diese zusätzliche Datei wird von der ksh sobald sie definiert ist, im Gegensatz zur bash, auch von Loginshells zusätzlich zur ~/.profile gelesen. Auf das Thema Umgebungsvariablen werde ich weiter unten noch genauer eingehen.

Aliase

Was kann man nun aber in seine Profildateien hinein schreiben? Ein Beispiel dafür sind Aliase. Aliase sind neue Namen für Befehle. Der Befehl ls zeigt den Verzeichnisinhalt an. Wenn jemand viel unter DOS gearbeitet hat, ist er vielleicht gewohnt, das der Befehl dir Verzeichnissinhalte anzeigt. Möchte ich also das bei Eingabe von dir der Befehl ls ausgeführt wird kann ich einfach schreiben:

alias dir=ls

Dies funktioniert auch zusammen mit Optionen. Wenn allerdings Leerzeichen in meiner Zeichenkette vorkommen muß ich durch einfache oder doppelte Anführungszeichen die Zeichenkette als zusammengehörig markieren:

alias dir='ls -A'

Diese Methode läßt sich auch gut verwenden um oft benutzte Befehl ein wenig abzukürzen. Hier ein paar Beispiele:

alias ll='ls -l'
alias la='ls -a'
alias c=cd
alias lh='ls -lh'
alias lc='wc -l'
alias g=grep
alias x=exit

Falls sie einen Befehl immer wieder mit ganz bestimmten Optionen benutzen, können sie über einen Alias ihre eigene Variante dieses Befehls erstellen:

alias myisofs='mkisofs -l -r -J'

Es ist genauso auch möglich einen bestehenden Befehl durch eine eigene Variante zu ersetzen:

alias ls='ls -F'

In diesem Fall wird von nun an ls immer als ls -F aufgerufen. Möchten sie dann das ursprüngliche ls aufrufen können sie dem Befehl ein Backslash voran stellen:

\ls

Mit dieser Methode konfigurieren übrigens die meisten Linuxdistributionen ls so das es automatisch Farben für Dateitypen anzeigt. Dazu ist im allgemeinen bereits folgender Alias gesetzt:

alias ls='ls --color=auto'

Aliase werden auch in Aliasen ersetzt. Ist ls bereits ein Alias wird es in Alias-Konstrukten die ebenfalls ls benutzen wiederrum ersetzt. Als Beispiel:

alias ls='ls -F'
alias la='ls -a'

In diesem Fall ergibt die Eingabe von la in Wirklichkeit den Befehl ls -F -a.

Gibt man einen Alias direkt auf der Kommandozeile an, wird man feststellen das dieser nur in dieser einen Instanz der bash gilt. Soll ein Alias also generell in jeder Shellinstanz definiert sein, so muß dieser Alias in einer Logindatei definiert werden. Hierfür eignet sich die ~/.bashrc.

Soll ein Alias wieder aufgehoben werden, gibt es noch den Befehl unalias:

unalias ls

Die Angabe von alias ohne weitere Argumente zeigt alle definierten Aliase an.

Ausgabeumlenkungen und Pipes

Manche Befehle erzeugen eine Ausgabe auf dem Bildschirm. Manchmal möchte man allerdings diese Ausgabe lieber in eine Datei umlenken. Dies ist in allen Shells möglich. Möchte man z. B. die Ausgabe von ls in eine Datei umleiten kann man folgendes Konstrukt verwenden:

ls > datei

Durch das > Zeichen werden die Ausgaben eines Befehls in eine Datei umgeleitet. Fehlermeldungen oder Statusmeldungen werden dabei nicht in die Datei umgeleitet, da es auf Unixsystemen grundsätzlich 2 Ausgabekanäle gibt: Die Standardausgabe und die Standardfehlerausgabe. Diese beiden Ausgabekanäle haben die Kanalnummer 1 für den Standardausgabekanal und die Kanalnummer 2 für den Standardfehlerkanal. Mit > leitet man nur die Standardausgabe um. Möchte man speziell die Standardfehlerausgabe umleiten muß die Kanalnummer mit angegeben werden:

2>

Mit folgender Syntax können beide Ausgabekanäle auf einmal in eine Datei geleitet werden:

ls > datei 2>&1

Durch das 2>&1 wird der Shell mitgeteilt, das die Standardfehlerausgabe (Kanal 2) genau dorthin geleitet werden soll, wo auch bereits die Standardausgabe (Kanal 1) hin geleitet wurde.

Existiert die Datei bereits in die eine Ausgabe umgeleitet wird, wird die Datei überschrieben. Möchte man lieber an eine existente Datei anhängen, kann man statt > einfach >> benutzen.

Pipes dienen dem Zusammenfügen mehrerer Befehle. Über eine Pipe läßt sich die Ausgabe eines Befehls als Eingabedateien für einen anderen Befehl verwenden. Das Zeichen für eine Pipe ist das |. Als Beispiel:

grep dhcpd /var/log/daemon | grep DHCPACK

In diesen Fall wird zuerst mit grep nach der Zeichenkette dhcpd innerhalb der Datei /var/log/daemon gesucht und danach alle Ergebniszeilen an an zweites grep Kommando das nach Zeilen die DHCPACK enthalten übergeben. Man erhält also am Ende eine zwei mal durch grep gefilterte Ausgabe.

Befehle verketten

Manchmal möchte man einen Befehl in Abhängigkeit von einem anderen Befehl ausführen, also z. B. nur dann wenn der erste Befehl erfolgreich war, soll der 2. Befehl durchgeführt werden. Dies läßt sich durch eine logische "und" Verknüpfung erreichen. Die Zeichenkette "&&" steht für eine logische Und-Verknüpfung, die Zeichenkette "||" für eine logische Oder-Verknüpfung. Jeder ausgeführte Befehl liefert eine sogenannten Exitstatus zurück. Ist dieser 0 so bedeutet dies, das der Befehl erfolgreich abgearbeitet wurde, ist er ungleich 0 dann ist ein Fehler aufgetreten. Die logischen Und- und Oder-Verknüpfungen bewirken das der Exitstatus des ersten Befehls ausgewertet werden und der folgende Befehl bei einem "&&" nur ausgeführt wird, wenn der erste Befehl erfolgreich war, bzw. umgekehrt bei einem "||" nur dann wenn der erste Befehl nicht erfolgreich (Exitstatus ungleich 0) war. Als Beispiel:

make && make install

Dies bewirkt das der Befehl make install nur dann ausgeführt wird, wenn der Befehl make erfolgreich ausgeführt wurde.

Hintergrundprozesse

Manche Programme benötigen eine gewisse Abarbeitungszeit, so das man gerne solch ein Programm im Hintergrund laufen lassen würde und sofort nach der Befehlseingabe seinen Prompt wieder bekommt um andere Befehle einzugeben. Dies ist in der Shell möglich indem man ein kaufmännisches & an den Befehl anhängt:

tar czf grosse_datei.tar.gz &

Dies läßt den Befehl tar xzf grosse_datei.tar.gz im Hintergrund laufen. Der Prompt erscheint sofort wieder um neue Befehle einzugeben. Dabei muß man jedoch beachten, das Befehle die eine Ausgabe auf dem Bildschirm erzeugen, dies auch weiterhin tun. Möchte man keine Meldungen des Programms sehen, empfiehlt es sich eventuell je nach Kommando die Ausgabe des Befehls entweder in eine Datei umzuleiten oder direkt nach /dev/null zu schicken und zwar für Standardausgabe und für Standardfehlerausgabe gleichermaßen:

find / -name "*.png" > ergebnis 2> /dev/null &

In diesem Fall wird eine Suche über das ganze Dateisystem (was längere Zeit dauern kann) nach *.png Dateien im Hintergrund angestossen. Das Suchergebnis wird dabei in die Datei ergebnis ausgegeben, eventuelle Fehler werden einfach nach /dev/null geleitet und damit ignoriert.

tar xzf grosse_datei.tar.gz 2> fehler &

In diesem Fall wird nur die Fehlerausgabe in die Datei fehler umgeleitet, zur späteren Analyse, falls ein Fehler auftritt. Die Standardausgabe wird nicht umgeleitet, da der Befehl tar ohne die Option v, ohnehin still arbeitet.

Jobs

Durch die Jobkontrolle der Shell kann man einen einmal gestarteten Prozess anhalten und dann sowohl im Hintergrund, als auch im Vordergrund wieder weiter laufen lassen. Dies kann z. B. praktisch sein, wenn man vergessen hat einen länger laufenden Prozess in den Hintergrund zu stellen. Läuft gerade ein Prozess im Vordergrund, kann man diesen über die Tastenkombination Strg + Z anhalten. Der Prozess ist dann nur gestoppt, nicht beendet und kann jederzeit wieder weiter laufen (Strg + C beendet übrigens einen Prozess). Nach dem drücken von Strg + Z bekommt man etwa folgende Ausgabe:

[1]+ Stopped sleep 120

Die Ausgabe sagt das das Kommando sleep 120 (welches nichts weiter macht als 120 Sekunden zu warten) gestoppt wurde und die Job-ID 1 bekommen hat. Die Shell weist jedem Job eine Job-ID zu, diese sollte nicht mit der Prozess-ID verwechselt werden. Job-IDs werden von der Shell zugewiesen, Prozess-IDs vom Betriebssystem.

Möchte ich das Kommando nun weiterlaufen lassen kann ich mich entscheiden dies entweder im Vordergrund, oder im Hintergrund zu tun. Möchte ich es im Vodergrund weiter laufen lassen, benutze ich das Kommando fg zusammen mit der Job-ID:

fg 1

Drücke ich danach erneut Strg + Z stoppe ich das Programm wieder. Mit dem Kommando bg kann ich das Kommando dann im Hintergrund laufen lassen und erhalte meinen Prompt sofort wieder:

bg 1

Für den Fall, das die Job-ID ohnehin 1 ist, kann man bei den Befehlen bg und fg die Job-ID auch weglassen.

Möchte ich wissen welche Jobs gerade in einer Shell laufen kann ich mir diese mit dem Befehl jobs auflisten lassen:

jobs

Einen im Hintergrund laufenden Job, kann ich mit fg natürlich auch wieder in den Vordergrund holen.

Variablen

Genau wie auch richtige Programmiersprachen kennt auch die Shell Variablen. Eine Variable wird einfach definiert indem man einem Namen einen Wert zuweist:

var="wert"

Dies weist der Variablen mit dem Namen "var" den Wert "wert" zu. Auf den Wert der Variablen kann dann mit einem vorangestellten $ zugegriffen werden:

echo $var

Die Variable kann sowohl Zeichenketten als auch Zahlen beinhalten. Sollte die Variable eine Zeichenkette mit Leerzeichen beinhalten muß diese in Anführungszeichen gesetzt werden (Ausnahme: die zsh macht dies intern automatisch):

echo "$var"

Manchmal muß man Variablen mitten in eine Zeichenkette einsetzen, so das es für die Shell schwierig wird zu erkennen was Teil des Variablennamens ist und was Teil der eigentlichen Zeichenkette ist. In diesem Fall muß der Variablenname in geschweifte Klammern eingefaßt werden:

echo "Zeichen${var}kette"

Variablennamen können aus Buchstaben, Zahlen und Unterstrichen bestehen, dürfen aber nicht mit Zahlen beginnen.

Variablen gelten in der Shell in der sie definiert wurden. Sollen sie auch in allen Subshells gelten (und damit in Prozessen, die von dieser Shell aus gestartet wurden), dann müssen diese exportiert werden. Dies geschieht durch das Schlüsselwort export:

export var="wert"

Alle in einer Shell gesetzten Variablen lassen sich über den Befehl set anzeigen:

set

Die Nutzung von Variablen bei der Scriptprogrammierung ist natürlich nötig, aber was kann ich mit Variablen bei der Konfiguration meiner Shell anfangen?

Umgebungsvariablen

Es gibt einige Variablen die in der Shell eine besondere Bedeutung haben. Diese nennt man Umgebungsvariablen und konfigurieren wichtige Aspekte wie etwa Heimatverzeichnis, Mailspooldatei und ähnliches. Die Namen dieser Variablen werden stets komplett in Großbuchstaben geschrieben. Alle Umgebungsvariablen kann man mit dem Befehl env anzeigen lassen:

env

In einer solchen Umgebungsvariablen ist auch der Inhalt des Prompts der Shell, abgespeichert. Die Variable heißt PS1:

echo $PS1

Wenn sie dieses Kommando in einer bash absetzen, werden sie womöglich etwas ähnliches wie das sehen: "\u@\h:\w\$". Diese kryptischen Kürzel haben eine spezielle Bedeutung innerhalb von PS1 in der Bash:

PS1-KürzelBedeutung
\uUsername
\hHostname
\wmomentanes Arbeitsverzeichnis
\Wletzter Teil des momentanen Arbeitsverzeichnisses
\$Falls mit Root-Rechten gearbeitet wird das Zeichen #, sonst $
\AUhrzeit im 24h-Format ohne Sekunden

Mit diesem Wissen kann man den Prompt der Shell beliebig verändern. Ein paar Beispiele:

PS1='\h:\W \A\$ '
PS1='\u@\h - Uhrzeit: \A -> '
PS1='\h Dir: \W \$ '

Wenn sie einen Prompt gefunden haben der Ihnen gefällt, schreiben sie die entsprechende PS1-Deklaration einfach in ihre ~/.bashrc Datei, damit diese bei jedem Start einer Shell geladen wird.

Die ksh kennt ebenfalls die Variable PS1 in dieser Bedeutung. Allerdings kennen die meisten Varianten der ksh nicht die Bedeutung der Escape-Sequenzen wie sie die bash verwendet. Eine Ausnahme bildet hier die unter OpenBSD verwendete Variante der pdksh, die die meisten der in der bash gültigen Escape-Sequenzen zur Verwendung innerhalb von PS1 ebenfalls versteht.

Eine weitere sehr wichtige Umgebungsvariable ist die Variable PATH. Der Inhalt der Variablen PATH besteht aus einer Reihe von Verzeichnissen, jeweils durch Doppelpunkt getrennt. Diese Verzeichnisse, sind die Verzeichnisse in denen ausführbare Befehle gesucht werden. Der Grund warum man Befehle wie ls oder grep einfach eingeben, kann ohne jeweils den kompletten Pfad zu diesen Kommandos mit angeben zu müssen, ist die Existenz dieser Variable. Tippt man ein Kommando ohne Pfadangabe ein, wird in allen Verzeichnissen die in PATH enthalten sind nach diesem Kommando gesucht. Man kann wenn man dies möchte auch einen zusätzlichen Wert an den bisherigen Wert von PATH anhängen, z. B.:

export PATH=$PATH:$HOME/bin

Dies setzt den neuen Wert von PATH, auf den alten Wert von PATH plus dem Verzeichnis $HOME/bin, wobei $HOME ebenfalls eine Umgebungsvariable ist, welche im allgemeinen der Pfad auf das Heimatverzeichnis des jeweiligen Benutzers ist.

Auf diese Art kann man in seinem Heimatverzeichnis ein Verzeichnis für ausführbare Programme wie z. B. selbstgeschriebene Scripts haben.

Funktionen

Funktionen in der Shell sind bereits so etwas wie kleine Scripts. Man kann sie auch als erweiterte Aliase bezeichnen, da man im Gegensatz zu einem Alias in einer Funktion nicht nur mehrere Kommandos zusammenfassen kann, sondern auch auf übergebene Parameter reagieren kann. Funktionen können auf 2 Arten deklariert werden:

funktionsname() { Befehle; }

oder

function funktionsname { Befehle; }

Es muß also entweder vor dem Funktionsname das Schlüsselwort function stehen oder direkt nach dem Funktionsnamen ein rundes Klammerpaar. Der eigentliche Funktionsblock, also der Teil in dem die auszuführenden Befehle stehen, wird dann in geschweifte Klammern eingefaßt. Innerhalb der geschweiften Klammer können dann beliebige Befehle stehen, die in der Funktion ausgeführt werden. Werden dabei mehrere Befehle in eine Zeile geschrieben, müssen die einzelnen Befehle mit einem Strichpunkt abgeschlossen werden.

Ein Beispiel:

info() {
echo -n "Username: "
whoami
echo -n "Hostname: "
hostname
echo -n "Betriebssystem: "
uname
}

Die Funktion tut nichts anderes als einige Informationen zum System ausgeben. Sie wird einfach über das Kommando "info" aufgerufen:

info

Funktionen gelten genauso wie Aliase nur direkt in der Shell in der sie definiert wurden. Möchte man eine bestimmte Funktion immer wieder benutzen, kann man sie in die ~/.bashrc schreiben.

Parameter für Funktionen

Manchmal möchte man Parameter in einer Funktion verwenden, um diese flexibler zu gestalten. Nehmen wir an sie durchsuchen häufiger eine bestimmte Logdatei mit grep. Um nicht jedesmal das ganze Kommando erneut einzugeben, definieren sie die Funktion suchlog:

suchlog() { grep qmgr /var/log/mail.log; }

Nun ist der Suchbegriff aber variabel, nicht immer suchen sie nur nach Zeilen die qmgr enthalten. Daher möchten sie an die Funktion einen Parameter übergeben, der als Suchausdruck benutzt wird. Dies läßt sich so realisieren:

suchlog() { grep "$1" /var/log/mail.log; }

$1 ist eine Spezialvariable. In $1 ist der erste Parameter enthalten der an einen Befehl übergeben wird. In $2 ist dann der zweite Parameter enthalten und so weiter bis $9. Sie können dies mit folgender Funktion testen:

function testparameter {
echo "Parameter 1: $1"
echo "Parameter 2: $2"
}

Wenn sie z. B. folgendes eingeben:

testparameter bla 13

Erhalten sie folgende Ausgabe:

Parameter 1: bla
Parameter 2: 13

Wir können jetzt also die Funktion etwas abwandeln:

suchlog() { grep "$1" /var/log/mail.log; }

Nun kann ein beliebiger Suchbegriff für die Funktion verwendet werden:

suchlog anvil

In diesem Fall wird der Befehl "grep anvil /var/log/mail.log" ausgeführt.

Tests

Der Testbefehl testet einfach ob eine Bedingung wahr oder falsch ist. Dieser Befehl wird meist in Scripts verwendet. Der Befehl kann sowohl als "test" oder auch als "[" geschrieben werden. Die Variante "[" erfordert dann beim Ende der Bedingung auch ein schließendes "]".

2 Beispiele:

test -f datei
[ $zahl -gt 5 ]

Im ersten Fall wird getestet ob die Datei "datei" existiert, im zweiten Fall wird getestet ob der Inhalt der Variablen zahl größer ist als 5. Test gibt einen Rückgabewert, den bereits vorher erwähnten Exitstatus zurück. Ist dieser Gleich 0 war der Test erfolgreich, die Bedingung ist also wahr, ist der Exitstatus von test ungleich 0, ist die Bedingung nicht wahr. Test kennt eine Reihe von Parametern für Dateitests, Zahlenvergleiche und Zeichenkettenvergleichen:

ParameterBedeutung
-fDatei existiert und ist eine normale Datei
-dDatei existiert und ist ein Verzeichnis
-rDatei existiert und ist lesbar
-xDatei existiert und ist ausführbar
-wDatei existiert und ist schreibbar
-eqnumerischer Vergleich auf Gleichheit
-nenumerischer Vergleich auf Ungleichheit
-gtnumerischer Vergleich auf größer als
-ltnumerischer Vergleich auf kleiner als
=Zeichenkettenvergleich auf Gleichheit
!=Zeichenkettenvergleich auf Ungleichheit

Dies sind längst nicht alle möglichen Parameter, in der Manpage von test finden sich weitere. Alle Bedingungen lassen sich durch ein vorangestelltes ! auch genau umkehren. Das ! wirkt also als Negierung.

Zusammen mit der oben erwähnten Befehlsverkettung lassen sich damit Befehle in Abhängigkeit von einem Test ausführen. Ich hatte dies bereits weiter oben vorgestellt, als ich in der ~/.bash_profile die ~/.bashrc geladen hatte nur falls diese auch existiert. Ein anderes Beispiel:

test -d /media/hdd/sicherung || mkdir /media/hdd/sicherung

Dies testet ob das Verzeichnis /media/hdd/sicherung existiert, ist dies der Fall wird nichts weiter gemacht, existiert es nicht, wird es angelegt.

Weiter zu

Literatur

Patrick Ditchen: Shell-Script Programmierung, Mitp-Verlag, 2006.


René Maroufi, dozent (at) maruweb.de
Creative Commons By ND