Teil 6: Funktionen und Module
Baukastensystem
Einfache Bash-Skripte werden in aller Regel heruntergeschrieben – die Befehle stehen in genau der Reihenfolge untereinander, in der sie später ausgeführt werden. Was bei wenigen Zeilen sinnvoll und leicht lesbar ist, wird bei langen Programmen zum Problem: Wenn das Auswerten der Kommandozeilen-Parameter direkt unter dem Einlesen einer Konfigurationsdatei steht und dann der Aufbau einer Bedienoberfläche folgt, verliert man schnell den Überblick über den Programmablauf. Im Vergleich sind die drei Anweisungen KonfigurationLesen, ParameterAuswertung und Bildmaske viel besser zu verstehen. Die Zusammenfassung von Befehlen zu einer Einheit, die einem bestimmten Zweck dient, nennt man Funktion.
Funktionelle Einheiten
Die Verwendung von Funktionen bietet neben besserer Übersichtlichkeit weitere Vorteile. Die Beispiel-Funktion Bildmaske, sie stellt eine einfache Bedienoberfläche dar, wird mehr als ein mal im Programm gebraucht. Nach jeder Bilschrimausgabe muss die Oberfläche aktualisiert werden – indem die Funktion aufgerufen wird. Soll die Darstellung später einmal verändert werden, brauchen Sie nur die Funktion anzupassen anstatt im Programm herumzusuchen und am Ende gar eine Stelle zu vergessen.
Bash-Funktionen sind fast vollwertige Programme, sie können Parameter übergeben bekommen, haben ihre eigenen Variablen und liefern am Ende einen Exit-Status an den Aufrufer zurück. Das erste Beispiel ParameterListe ist eine Funktion, die alle Parameter ausgibt:
function ParameterListe () { echo "$@" }
Das einleitende Schlüsselwort function kann entfallen, wichtig sind nur die beiden runden Klammern hinter dem Namen der Funktion. Alle Anweisungen, die zur Funktion gehören, stehen in den geschweiften Klammern. Etwas verwirrend ist anfangs die Handhabung der Spezial-Variablen $1, $2, $3 usw. Bei Programmaufruf enthalten sie die Kommandozeilen-Parameter, analog beim Aufruf einer Funktion die Funktions-Parameter. Das bedeutet jedoch, dass Sie innerhalb einer Funktion nicht an die Kommandozeilen-Parameter herankommen – wenn nötig müssen Sie sie beim Aufruf mitgeben.
Der Aufruf einer Funktion erfolgt analog zu dem jedes anderen Programms – hinter dem Namen werden die Parameter als mit Leerzeichen, Tabulator oder Enter getrennte Liste übergeben:
ParameterListe $1 $2 ParameterListe Hallo Welt
Ist die Funktion abgearbeitet, wird das Programm mit der dem Aufruf folgenden Anweisung fortgesetzt, meist ist das die nächste Zeile. Funktionen haben grundsätzlich einen Rückgabewert. Er entspricht dem Ergebnis des letzten Befehls, der in der Funktion abgearbeitet worden ist, er kann aber auch mittels return explizit gesetzt werden.
Geltungsbereich von Variablen
Für die in Funktionen verwendeten Variablen gibt es einige Besonderheiten zu beachten. Ohne Funktionen und Blöcke gelten alle Variablen global im gesamten Programm, $i bezeichnet überall die gleiche Variable. Solche globale Variablengelten ebenfalls innerhalb von Funktionen. Folgendes Beispiel-Programm soll das dadurch entstehende Problem veranschaulichen:
001 #!/bin/bash 002 Trennzeile () 003 { 004 i=0 005 while [ $i -lt 10 ]; do 006 echo -n "*" 007 i=$[$i+1] 008 done 009 echo 010 } 011 012 i=1 013 while [ $i -lt 4 ]; do 014 Trennzeile 015 echo $i 016 i=$[$i+1] 017 done
Die Funktion Trennzeile benutzt die Variable $i in der While-Schleife, um eine Zeile mit zehn Sternen auszugeben. Die zweite While-Schleife des Listings im Hauptprogramm soll Trennzeile drei mal aufrufen und dabei jeweils den Wert $i ausgeben. Das Ergebnis sollte so aussehen:
1 2 3
Stattdessen erhalten wir aber nur zwei Zeilen:
10
Der Teufel steckt im Detail: Bei Aufruf des Programms wird die Funktion Trennzeile zunächst nur gelesen, aber nicht ausgeführt. Als erstes wird $i in Zeile 12 auf eins gesetzt, danach die While-Schleife von Zeile 13 bis 17 abgewickelt. Bei Aufruf von Trennzeile in Zeile 14 ist $i noch eins, doch die Variable wird in Zeile 7 verändert. Am Ende der Funktion (Zeile 9) ist $i schließlich zehn. Das Programm wird nach Abarbeitung der Funktion in Zeile 15 fortgesetzt, gibt auf dem Bildschirm 10 aus und erhöht $i um eins. Vor erneuter Ausführung der Schleife wird die Bedingung aus Zeile 13 überprüft, und stellt sich als falsch heraus – die While-Schleife wird verlassen und das Programm endet.
Die Ursache ist die Verwendung der globalen Variablen $i innerhalb unserer Funktion, damit greifen wir ungewollt in das Hauptprogramm ein. Doch wie kann man sicher gehen, nicht mit den Variablen-Namen des Hauptprogramms oder anderer Funktionen ins Gehege zu kommen?