Shell-Workshop, Teil 3
Shell-Workshop, Teil 3.
Shellscripts
Eine Reihe von Befehlen kann man in einer Datei abgespeichern, um diese bei Bedarf immer wieder ausführen zu lassen. Es genügt tatsächlich, die Befehle mit Hilfe eines gewöhnlichen Text-Editors in einer Datei zu hinterlegen und dann die Bash selbst aufzurufen mit den Dateinamen als Argument. So könnte man etwa ein Backup (auf relativ unelegante Weise) erstellen lassen:
cd # Wechselt ins Heimverzeichnis tar czf /tmp/backup.tgz *
Wenn man diese Befehle in eine Datei mit dem Namen "mkbu.sh" (als Abkürzung für "MaKe BackUp SHellscript") schreibt, kann man sie so ausführen lassen:
bash mkbu.sh
Es wird dann ein Archiv des Homeverzeichnisses erstellt und unter /tmp/backup.tgz abgespeichert. Beim nächsten Aufruf des Scripts wird das bestehende Archiv - wenn es sich noch dort befindet - überschrieben.
Aber man kann ein Shellscript auch ausführbar machen wie ein richtiges Programm, indem man bei den Berechtigungen der Datei x-Bit setzt:
chmod a+x mkbu.sh
Wie ein Programm ausgeführt wird, ist Sache des Linux-Kernels. Diesem muss man aber nun noch irgendwie sagen, dass die Datei von der Bash verarbeitet werden soll. Dass es sich um ein Script handelt, dass von einem Interpreter ausgeführt wird, kann man definieren, indem man "#!" als erste Zeichenfolge in die Datei einfügt. Den Interpreter (in diesem Fall also die Bash) schreibt man direkt dahinter, wobei der vollständige Pfad anzugeben ist (also "/bin/bash"). Zuletzt folgt noch ein Zeilenumbruch. Die erste Zeile eines Shellscripts muss also so aussehen:
#!/bin/bash
Wenn die Datei nun wie oben beschrieben ausführbar gemacht wurde, dann kann das Script nun mit dem folgenden Befehl ausgeführt werden:
./mkbu.sh
Die Angabe des Pfades ("./" ist das aktuelle Verzeichnis) ist dabei notwendig, weil Programme nur in den Pfaden gesucht werden, die in der Shell-Variablen PATH stehen, nicht jedoch im aktuellen Verzeichnis.
Bedingte Ausführung
Weiter oben wurde bereits erläutert, wie man mehrere Anweisungen in einem Shellscript zusammenfassen kann. Das alleine reicht oftmals aber nicht aus, weil dabei nicht die Möglichkeit gegeben ist, auf verschiedene Situationen individuell zu reagieren.
Die Bash verfügt wie jede andere Shell über ein Feature, durch welches das Ausführen bestimmter Kommandos von einer Bedingung abhängig gemacht werden kann. Als Bedingung wird dabei der Fehlerstatus eines anderen Kommandos herangezogen, d.h. wenn ein Kommando erfolgreich ausgeführt werden konnte, werden auch die anderen ausgeführt, sonst nicht.
Die Bash stellt zu diesem Zweck eine if-Anweisung zur Verfügung, deren allgemeine Form so aussieht:
if bedingung then anweisungen fi
Wie üblich kann statt eines Zeilenumbruchs dabei auch jeweils ein Semikolun geschrieben werden. "anweisungen" steht für eine beliebige Anzahl Kommandos, die je durch ein Semikolon oder einen Zeilenumbruch voneinander getrennt sind und abgearbeitet werden, wenn das als Bedingung verwendete Kommando erfolgreich war (was das bedeutet, darauf werden wir gleich näher eingehen). Die Anweisungen müssen nicht unbedingt eingerückt werden, aber es ist empfehlenswert, das zu tun, da es die Lesbarkeit erhöht. Das ganze Konstrukt endet mit "fi", also "if" rückwärts. Ein Schlüsselwort umgedreht zu schreiben, um das Ende des jeweiligen Blocks zu signalisieren, mag etwas seltsam erscheinen, doch man findet dies auch an anderen Stellen: Die case-Anweisung der Bash wird tatsächlich mit "esac" abgeschlossen.
Als Bedingung für das if-Konstrukt kann zwar jedes beliebige Kommando zum Einsatz kommen, aber in den allermeisten Fällen wird das Programm "test" an entsprechender Stelle benutzt. Wie der Name andeutet, wird hierbei nur ein Test durchgeführt und keine Aktion ausgeführt.
Man kann mit "test" unter anderem die Berechtigungen einer Datei überprüfen, also beipielsweise, ob eine Datei gelesen werden kann. Dazu kann man "test" die Option -r (was für "read", also Leseberechtigung steht) und den Namen der Datei übergeben, deren Berechtigungen geprüft werden sollen. Wenn man dies nun als Bedingung von if verwendet, sieht das so aus:
if test -r /usr/local/scripts/backup then bash /usr/local/scripts/backup fi
Hier würde überprüft, ob die genannte Datei gelesen werden kann (was nicht gegeben ist, wenn sie nicht existiert; es wird also implizit auch die Existenz der Datei getestet). Falls es möglich sein sollte, sie einzulesen, wird eine Bash gestartet, um die Datei als Shellscript abzuarbeiten.
Hierbei gibt es nur ein kleines Problem: Es gibt - zumindest theoretisch - die Möglichkeit, dass /usr/local/scripts/backup gar keine Datei ist, sondern ein Verzeichnis. Der reine Test auf Lesbarkeit reicht also nicht aus. Man kann mit "test" auch prüfen, um was es sich handelt. So überprüft die Option -d etwa, ob es sich um ein Verzeichnis ("Directory") handelt, -L (ein großes L) dient zum Erkennen von symbolischen Links und -f testet, ob es sich um eine gewöhnliche Datei handelt. Um sowohl zu verifizieren, ob es eine normale Datei ist als auch, ob sie lesbar ist, könnte man natürlich zwei if-Anweisungen verschachteln:
if test -r /usr/local/scripts/backup then if test -f /usr/local/scripts/backup then bash /usr/local/scripts/backup fi fi
Das ist allerdings nicht sonderlich elegant, denn man kann es wesentlich kürzer schreiben. Das Programm "test" bietet selbst die Möglichkeit, mehrere Tests zugleich zu überprüfen. Eine Und-Verknüpfung (beide Tests müssen erfolgreich sein, damit das Endergebnis "Erfolg" lautet) wird realisiert, indem man zwischen die beiden Tests die Option -a schreibt (für "and", auf Deutsch "und"):
if test -r /usr/local/scripts/backup -a -f /usr/local/scripts/backup then bash /usr/local/scripts/backup fi
Man sieht oftmals Shellscripts, in denen nur überprüft wird, ob etwas eine existierende Datei ist und wenn das zutrifft, wird sie verarbeitet. Der Grund dafür ist einfach, dass bestimmte Dateien (wie ~/.bashrc, die oft von ~/.bash_profile eingebunden wird) normalerweise gelesen werden dürfen, wenn sie existieren. Falls dies einmal nicht zutreffen sollte, ist dies als Konfigurationsfehler zu betrachten, weshalb die dann aufgrund fehlender Berechtigungen ausgegebene Fehlermeldung angebracht ist.
Wer nun einen Blick in die Manualpage von "test" geworfen hat, der wird möglicherweise ein wenig überrascht gewesen sein, denn man kann als letztes Argument optional eine schließende eckige Klammer angeben, die ohne Auswirkungen bleibt. Der Hintergrund davon ist, dass es ein Synonym für "test" gibt, das einfach nur "[" heißt. Dadurch kann man Prüfungen auch etwas übersichtlichert wie folgt schreiben:
if [ -f /usr/local/scripts/backup ] then bash /usr/local/scripts/backup fi