Einführung in die Bash-Programmierung

Aus LinuxUser 02/2005

Einführung in die Bash-Programmierung

Fertighaus im Eigenbau

Skripte sollen meist wiederkehrende oder lästige Arbeiten automatisieren. Die Standard-Shell Bash stellt Ihnen dazu eine ganze Reige von Funktionen bereit. Dieser Artikel erklärt, wie Sie ihre eigenen Shell-Skripte schreiben.

Bash-Skripte taugen nur für einfache Aufgaben, so lautet das landläufige Vorurteil. In der Version 1.x, wie man sie noch auf alten Red-Hat-Systemen findet, eignete sich die Bash tatsächlich nur wenig für komplexe Aufgaben. Wer umfangreichere Arbeiten automatisieren wollte, griff daher zur csh oder tcsh – oder bemühte gleich eine Hochsprache.

Heute installieren alle Distributionen standardmäßig die Version 2.x der Bash, die über sehr viel mehr und komplexere Funktionen verfügt als ihre Vorgänger-Versionen. Weiß der Programmierer die Leistungsfähigkeit der “Bourne Again Shell” richtig einzusetzen, benötigt er kaum noch externe Programme. Das erhöht die Geschwindigkeit: Mit jedem externen Programmaufruf startet eine neue Instanz der Bash, die nur diesen Befehl ausführt und sich anschließend beendet.

Bremsklotz externe Aufrufe

Externe Prozesse werden besonders dann zum Problem, wenn sie nur kurz aufgerufen werden. Folgendes Skript ist als “Fork-Bomb” berüchtigt. Es tut nichts anderes, als sich selbst zwei mal aufzurufen:

#!/bin/bash
$0 &
$0

Für jeden Aufruf eines externen Programms – “$0” steht hierbei für den Namen des gerade laufenden Skripts – muss der Kernel Speicher reservieren, einen Prozess anlegen, Zeitscheiben umverteilen und vieles mehr. Endet ein Programm wieder, geht es an die Aufräumarbeiten – all das verbraucht nicht unerheblich Systemressourcen. Im Fall der Fork-Bombe benutzt ein böswilliger Anwender diesen Umstand, um das ganze System lahmzulegen, in dem er das einfache Skript unendlich oft aufrufen lässt. Im Ergebnis starten zehntausende Instanzen des Skripts, bis der Speicher voll läuft und keine weiteren Prozesse mehr gestartet werden können. Der Rechner friert nach einigen Sekunden komplett ein, es hilft nur noch ein Hardware-Reset.

Das Listing der Fork-Bombe zeigt den grundlegenden Aufbau eines Bash-Skripts. In der ersten Zeile steht hinter den Zeichen “#!” der Name des Interpreters, der dieses Skript versteht – in unserem Fall /bin/bash. Sie sollten hier nicht, wie häufig zu sehen, einfach /bin/sh eintragen, weil dies je nach System nur ein symbolischer Link ist, der auch auf einen anderen Interpreter zeigen kann.

Einfache Befehlslisten

Der Rest des Programms besteht im einfachsten Fall aus einer Reihe von Befehlen, die nacheinander ausgeführt werden – genau so, wie Sie sie an der Kommandozeile eingeben würden. Dabei wartet die Bash stets, bis ein Kommando komplett ausgeführt wurde, das aufgerufene Programm sich also beendet hat – es sei denn, dem Kommando folgt ein kaufmännisches Und (&). In diesem Fall ruft die Bash das Programm nur auf und macht unmittelbar nach dem Aufruf mit dem nächsten Befehl weiter. Auf diese Weise lassen sich Hintergrundprogramme oder Daemons starten, das aufgerufene Programm läuft also parallel zum Mutterprozess.

Im Beispielprogramm wird “$0” zunächst im Hintergrund aufgerufen und dann noch einmal “$0” im Vordergrund – die Bash wartet also, bis das zweite Programm abgeschlossen ist, bevor sich das Skript selbst beendet.

Bei “$0” handelt es sich um eine Variable, genauer gesagt um eine Spezialvariable: Sie enthält den Namen des aktuell laufenden Skripts. Haben Sie das Beispielprogramm etwa mit “./forkbomb” aufgerufen, setzt die Bash an Stelle von “$0” stets “./forkbomb” ein. “$1” stünde für den ersten Parameter, den Sie beim Aufruf des Skripts angegeben haben, “$2” für den zweiten, und so weiter. Nach oben ist keine Grenze gesetzt.

Dollar oder nicht?

Die Benutzung der Variablen ist für Einsteiger etwas verwirrend. So müssen Sie das Dollar-Zeichen immer dann vor den Variablennamen schreiben, wenn Sie den Inhalt der Variablen erhalten wollen. Möchten Sie dagegen der Variablen einen Wert zuweisen, dürfen Sie das Dollar-Zeichen nicht benutzen:

#!/bin/bash
H="Hallo"
W="Welt"
echo "$H $W"

In den Zeilen 2 und 3 weisen Sie den Variablen H und W die Werte “Hallo” und “Welt” zu. Deshalb dürfen Sie das Dollar-Zeichen nicht verwenden. Vor dem Variablennamen im Zeile 4 signalisiert es hingegen der Bash, dass Sie an dieser Stelle nicht die Buchstaben “H” und “W” ausgeben wollen, sondern den Inhalt der Variablen “H” und “W”. Die Bash setzt also implizit an Stelle von “$H” und “$W” die Zeichenfolge “Hallo” und “Welt” ein.

Für “$H” und “$W” gibt es noch eine zweite Schreibweise, die Sie bevorzugen sollten: “${H}” und ${W}”. Der Vorteil der zweiten Schreibweise ist, dass die Bash klar erkennen kann, wo der Variablenname endet:

#!/bin/bash
Hallo="Hello"
Welt="World"
HalloWelt="$Hallo $Welt"
echo "$HalloWelt"

Dies ist ein einfaches Beispiel für eine englische Übersetzung – für eine spanische würden Sie der Variablen Hallo den Wert “Holla” und Welt “Mundo” zuweisen. Problematisch ist jedoch die vierte Zeile: Was soll die Bash an dieser Stelle ausgeben? Den Inhalt der Variablen Hallo und die Buchstaben “Welt” dahinter, oder den Inhalt der Variablen HalloWelt? Unzweideutig wäre hingegen die folgenden Schreibweisen:

"echo ${HalloWelt}"
"echo ${Hallo}Welt"

Im ersten Fall gibt die Bash den Inhalt der Variablen HalloWelt aus, im zweiten den Inhalt der Variablen Hallo gefolgt von der Zeichenfolge Welt – also “HelloWelt”.

Tückische Leerzeichen

Das vorletzte Beispiel zeigt, warum Sie Zeichenketten in Anführungzeichen einschließen sollten: Das Leerzeichen ist für die Bash ein Trenner zwischen den Parametern. In der dritten Zeile wird aus den Variablen Hallo und Welt die Zeichenkette “Hello World” zusammengebaut – auch ohne Anführungszeichen prinzipiell kein Problem. Den Unterschied sehen Sie bei folgenden Schreibweisen:

$HalloWelt=${Hallo}   ${Welt}
$HalloWelt="${Hallo}   ${Welt}"

In beiden Fällen stehen im Listing drei Leerzeichen zwischen den Variablen. Bei der ersten Zeile trennt in der Ausgabe jedoch nur ein Leerzeichen die zusammengesetzte Zeichenkette, da die Bash Spaces als Trenner zwischen zwei Parametern ansieht – der erste Parameter enthält “Hello”, der zweite “World”. Diese Parameter trennt die Bash beim Zusammenbau dann nur noch mit einem Leerzeichen.

Bei der zweiten Zeile bleiben dagegen die drei Leerzeichen in der Ausgabe erhalten: Die Anführungszeichen signalisieren der Bash, dass es sich hierbei um ein zusammengehöriges Konstrukt handelt, also eine einzige Zeichenkette und damit nur einen Parameter.

Das gleiche gilt für die Ausgabe des Textes mit echo: Geben Sie die Variable HalloWelt ohne umschließende Anführungszeichen ein, expandiert die Bash den Aufruf zu:

echo Hello   World

Damit bekommt der Befehl echo zwei Parameter übergeben, “Hello” und “World” – und trennt diese nur mit einem Leerzeichen bei der Ausgabe. Mit umschließenden Anführungszeichen wird der Text wieder zu einem Parameter zusammengefasst und echo gibt drei Leerzeichen zwischen “Hello” und “World” aus.

Kontrollstrukturen und Schleifen

Die einfachste Kontrollstruktur ist if. Die Anweisung überprüft, ob eine Bedingung gegeben ist, und führt abhängig vom Resultat dann weitere Befehle aus. Das folgende Beispiel prüft, ob es die Datei /etc/mtab gibt:

#!/bin/bash
if [ -e /etc/mtab ]; then
  echo "/etc/mtab gefunden"
else
  echo "Keine mtab gefunden"
fi

In diesem Beispiel verstecken sich gleich mehrere Besonderheiten. So erwartet die Kontrollstruktur hinter dem Schlüsselwort if einen ausführbaren Befehl – in unserem Fall [ -e /etc/mtab ]. Die eckige Klammer dient dabei nur als andere Schreibweise für den Befehl test, der mit den Parametern -e /etc/mtab aufgerufen wird. Existiert die angegebene Datei, liefert test den Wert “0” als Rückgabewert (Exit Value), andernfalls “1”. In diesem Punkt unterscheidet sich die Logik der Bash von anderen Programmiersprachen: Der Wert 0 gilt stets als wahr, Werte ungleich 0 dagegen als falsch.

Rückgabewerte

Nach Unix-Standard liefern alle Programmen beim Beenden einen Exit Value an das aufrufende Programm zurück. Dabei handelt es sich jedoch nicht um eine Bildschirmausgabe: In der Bash erhalten Sie den Exit Value des letzten Befehls über die Spezial-Variable “$?”.

Ein häufiger Flüchtigkeitsfehler bei den eckigen Klammern stellt das Fehlen des Leerzeichens hinter der öffnenden oder vor der schließenden Klammer dar. Auch das Semikolon dürfen Sie nicht vergessen – sonst geht die Bash davon aus, dass Sie den Befehl noch fortsetzen möchten. Anstelle von test können Sie prinzipiell auch jedes andere Programm oder Skript aufrufen, je nach dem, was Sie für Ihr Programm benötigen.

Als wohl wichtigstes Schleifenkonstrukt dient for, das gleich zwei Verwendungsformen kennt:

#!/bin/bash
for ((i=1; ${i}<=10; i++)); do
  echo ${i}
done
for d in `ls -1 /etc`; do
  echo ${d}
done

Die erste Verwendungsform in den Zeilen 2 bis 4 kennen viele Leser sicher von anderen Programmiersprachen: Die mit dem Wert “1” initialisierte Variable i wird mit jedem Schleifendurchlauf um eins erhöht, solange ihr Wert kleiner oder gleich 10 bleibt. Innerhalb der Schleife gibt echo den aktuellen Wert der Variablen i aus.

Listenverarbeitung

Die zweite Verwendungsform in den Zeilen 5 bis 7 bietet die interessanteren Möglichkeiten, da sie die Verarbeitung von Listen erlaubt: Hinter dem Schlüsselwort for steht der Name der Variablen, der ein Element zugewiesen werden soll – deshalb darf hier kein Dollar-Zeichen vorkommen. Hinter in steht die Liste von Elementen, durch Leerzeichen resepktive Tabulatoren oder Zeilenumbrüche getrennt.

Das Beispiel nutzt das externe Kommando ls -1 /etc, um die Liste zu erzeugen. Das bewirken die umgedrehten Hochkommas (“backticks”), die Sie mit [Shift][‘] erhalten. Als Ergebnis liefert der Befehl eine Liste aller Dateien und Verzeichnisse, die sich in /etc finden.

Bei jedem Schleifendurchlauf entfernt die Bash das erste Element der Liste und weist es der Variablen – hier also d – zu. Dann werden die Kommandos innerhalb des Blocks ausgeführt. Im Beispiel gibt echo einfach den Inhalt der Variablen aus, also den Dateinamen. Im Ergebnis ist die Ausgabe identisch mit dem, was ein direktes ls -1 /etc/ anzeigen würde.

Die ganze Welt?

Dieser Artikel reißt nur die allerwichtigsten Funktionen und Konstrukte der Bash an, die Sie für fast alle Skripte benötigen. Die Bash ist jedoch viel leistungsfähiger, als die meisten Anwender annehmen, und beherrscht selbst solch komplexe Themen wie Arrays und Textbearbeitung.

Die Programming Corner, unsere siebenteiligen Artikelserie aus LinuxUser 12/2000 bis 02/2002 ([1] bis [7]), führt Sie ausführlich in die Kunst der Bash-Programmierung ein und zeigt Ihnen, wie Sie selbst komplexe Programme effizient und mit wenigen Code-Zeilen in der Shell lösen.

Infos

[1] Mirko Dölle: “Hallo Welt” (Teil 1), LinuxUser 12/2000, S. 38 http://www.linux-user.de/ausgabe/2000/12/038-pcorner/pcorner1.html

[2] Mirko Dölle: “Im Märze der Bauer…” (Teil 2), LinuxUser 01/2001, S. 48 http://www.linux-user.de/ausgabe/2001/01/048-pcorner/pcorner2.html

[3] Mirko Dölle: “Wortfetzen” (Teil 3), 02/2001, S. 49 http://www.linux-user.de/ausgabe/2001/02/049-pcorner/pcorner3.html

[4] Mirko Dölle: “Ihre Papiere bitte…” (Teil 4), LinuxUser 04/2001, S. 58 http://www.linux-user.de/ausgabe/2001/04/058-pcorner/pcorner4.html

[5] Mirko Dölle: “Checkpoint Charlie” (Teil 5), LinuxUser 05/2001, S. 48 http://www.linux-user.de/ausgabe/2001/05/048-pcorner/Kontrollstrukturen-2.html

[6] Mirko Dölle: “Baukastensystem” (Teil 6), LinuxUser 07/2001, S. 52 http://www.linux-user.de/ausgabe/2001/07/052-pcorner/pcorner6.html

[7] Mirko Dölle: “Dialogregie” (Teil 7), LinuxUser 02/2002, S. 42 http://www.linux-user.de/ausgabe/2002/02/042-pcorner/pcorner7.html

LinuxUser 02/2005 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben