![]() |
|
||||||||||||||||||
|
|||||||||||||||||||
|
|||||||||||||||||||
|
||
|
TFT Monitor bei
Mercateo kaufen.
Neues Netbook? Ein Preisvergleich lohnt sich. Bei uns finden sie Notebooks, PDAs und Drucker mit Testberichten und Tipps. Diamant Buchhaltungssoftware – transparent und detailliert auch für die Konzernbuchhaltung. Günstige Shareware Programme als direkte Downloads im Software Portal. Bis zu 70% sparen durch Preisvergleich. |
||
|
Teil 6: Funktionen und ModuleBaukastensystemvon Mirko Dölle |
|
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.
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.
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 Variablen gelten 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?
Wir benötigen Variablen, die lediglich für unsere Funktion gelten und auf keinen Fall mit globalen Variablen kollidieren: Lokale Variablen. Sie werden durch Voranstellen des Schlüsselworts local bei der ersten Benutzung oder am Anfang der Funktion definiert und gelten nur für diese eine Funktion. Das folgende Listing zeigt die veränderte Funktion Trennzeile aus unserem Beispiel:
002 Trennzeile ()
003 {
004 local i=0
005 while [ $i -lt 10 ]; do
006 echo -n "*"
007 i=$[$i+1]
008 done
009 echo
010 }
Wir verwenden $i nach wie vor im Hauptprogramm (Zeile 13 und 16) und in der Funktion (Zeile 5 und 7), offensichtlich muss es hier eine Kollision geben. Das Problem wird durch Überdeckung bereits benutzter Namen gelöst: Die Variable $i innerhalb von Trennzeile ist unabhängig von ihrer globalen Namensvetterin, wir haben also zwei verschiedene $i. Der Nachteil ist, dass wir jetzt in Trennzeile nicht mehr an die globale Variable $i herankommen. Manchmal benötigt man eine globale Variable als Anfangswert, ohne sie im weiteren Verlauf der Funktion zu verändern zu dürfen, man müßte in der Funktion mit einer neuen Variablen arbeiten. Dafür gibt es einen Trick -- wir weisen die globale Variable $i beim Anlegen der lokalen Variable $i als Anfangswert zu. Dabei nutzen wir aus, dass die Bash zunächst den Teil rechts des Gleichheitszeichens auswertet und das Ergebnis der linken Seite zuweist.
002 Trennzeile ()
003 {
004 local i=$i
005 while [ $i -lt 10 ]; do
006 echo -n "*"
007 i=$[$i+1]
008 done
009 echo
010 }
Im Ergebnis verkürzt sich damit unsere Trennzeile um jeweils ein Sternchen:
********* 1 ******** 2 ******* 3
Eine Funktion hat genau wie ein Programm einen Rückgabewert (Exit-Code), der über die Spezial-Variable $? unmittelbar nach Ende der Funktion abgefragt werden kann. Standardmäßig ist der Rückgabewert einer Funktion identisch mit dem Rückgabewert des zuletzt ausgeführten Befehls. Bei Trennzeile war der letzte Befehl echound somit der Rückgabewert stets 0. Mittels return können Sie andere Werte zurückgeben. Hier eine Funktion, die stets "falsch", also einen Wert ungleich null, liefert:
Falsch ()
{
return 1
}
return verlässt die Funktion auf der Stelle und ist besonders dann interessant, wenn in einer Funktion Fehler auftreten können. DateiLesen wird, falls die als Parameter $1 übergebene nicht existiert oder nicht lesbar ist, beendet und liefert als Rückgabewert eins. Andernfalls wird der Inhalt in $Inhalt gespeichert und der Rückgabewert dieser Aktion (dieser ist immer null, eine Zuweisung ist stets erfolgreich) verwendet.
DateiLesen ()
{
if [ ! -r $1 ]; then
return 1
fi
Datei="`cat $1`"
}
Durch die Aufgliederung eines Programmes in Funktionen kann es vorkommen, dass erst in einer Funktion ein Fehler festgestellt wird, der die weitere Programmausführung unmöglich macht, wie etwa zu wenig freie Festplattenkapazität. Diesen Fehler kann man an den Aufrufer zurückmelden, einfacher ist es aber das Programm sofort abzubrechen. Dazu dient der Befehl exit, der genau wie return einen Rückgabewert erhalten kann:
DateiLesen ()
{
if [ ! -r $1 ]; then
exit 1
fi
Datei="`cat $1`"
}
Mit Funktionen kann man ein Programm nicht nur kürzer und besser lesbar machen, bei ähnlichen Anwendungen kann man die Funktionen auch leicht in einem anderen Programm wiederverwenden. Wenn Sie eine Reihe von Funktionen geschrieben haben, sollten Sie sie in einzelnen Modulen zusammenfassen. Ein Modul enthält eine oder mehrere Funktionen aus verwandten Aufgabenbereichen wie zum Beispiel Dateioperationen, Bildschrim-Ein-/Ausgabe oder Stringverarbeitung. Bei der Bash ist jedes Modul in einer eigenen Datei untergebracht. Im Unterschied zu Programmen sind die Module nicht ausführbar und enthalten auch nicht den bei Bash-Programmen üblichen "Kommentar" #!/bin/bash in der ersten Zeile. Die einzelnen Module werden mit dem Source-Befehl in das Programm eingebunden:
#!/bin/bash source dateioperationen source einausgabe . stringverarbeitung
Der Source-Befehl kann mit einem Punkt (vierte Zeile) abgekürzt werden, diese Schreibweise findet man in der Praxis sehr oft. Trifft die Bash auf eine Source-Anweisung, wird im Speicher die gesamte Datei anstelle des Source-Befehls eingefügt und komplett abgearbeitet. Auf diese Weise ist es möglich, den Source-Befehl auch in Modulen einzusetzen, ihn also zu verschachteln.
Ein weiteres Anwendungsgebiet für source ist das Einlesen von Konfigurationsdateien. Bei SuSE-Distributionen ist die Datei /etc/rc.config eine der zentralen System-Konfigurationsdateien. Sie enthält lediglich Kommentare und Variablen-Zuweisung in Bash-Schreibweise und wird einfach mittels Source-Befehl von den jeweiligen Skript-Dateien eingelesen. In Kasten 1 finden Sie ein Beispiel zum Einlesen einer Konfigurationsdatei.
| Kasten 1: Einlesen einer Konfigurationsdatei |
|
Datei: .config
# Zu verwendende Sprache LANG="de_DE" Datei: dateioperationen
KonfigurationPruefen ()
{
# Name der Datei erforderlich (1. Parameter)
if [ ! $# -eq 1 ]; then
return 1
# Datei muss lesbar sein
elif [ ! -r $1 ]; then
return 2
fi
return 0
}
Datei: konfiguration
#!/bin/bash . dateioperationen KonfigurationPruefen .config && source .config echo $LANG |
Im Hauptprogramm konfiguration finden Sie beide Verwendungsformen des Source-Befehls. In der zweiten Zeile wird zunächst das Modul dateioperationen geladen, in dem sich die Funktion KonfigurationPruefen befindet. Die dritte Zeile enthält eine Verkettung zweier Befehle: KonfigurationPruefen liefert wahr, also null, wenn die Datei .config lesbar ist. Nur dann wird sie mittels source eingebunden.
Es ist generell zu empfehlen, die Existenz jedes Moduls vor dem Einbinden zu überprüfen. Fehlt ein Modul, im folgenden Beispiel dateioperationen, gibt die Bash eine entsprechende Fehlermeldung aus, versucht das Skript aber dennoch laufen zu lassen. Im schlimmsten Fall führt das zu Folgefehlern durch fehlende Funktionen:
./konfiguration: dateioperationen: No such file or directory ./konfiguration: KonfigurationPruefen: command not found
Funktionen und Module sind der Schlüssel zu strukturierten, lesbaren und wiederverwendbaren Programmen. Als Nebeneffekt lässt sich damit auch eine gute Arbeitsteilung realisieren: Während sich ein Programmierer um die Dateiverwaltung kümmert, entwirft ein anderer die Benutzeroberfläche -- nur auf diese Weise konnten Linux und die GNU-Programme von einer großen Gemeinde entwickelt werden. In einer endlos langen Ansammlung von Befehlen wäre längst der Überblick verloren gegangen.
Damit endet Teil sechs. Im siebten und letzten Teil des Programming Corners werden wir zum großen Finale einläuten und anhand eines Beispiel-Programms noch einmal alle Themen der vergangenen Teile ansprechen und veranschaulichen. (mdö)
Dieser Online-Artikel kann Links enthalten, die auf nicht mehr vorhandene Seiten verweisen. Wir ändern solche "broken links" nur in wenigen Ausnahmefällen. Der Online-Artikel soll möglichst unverändert der gedruckten Fassung entsprechen.
Druckerfreundliche Version |
Feedback zu dieser Seite
|
Datenschutz |
© 2010 Linux New Media AG
[Linux-Magazin]
[LinuxUser]
[EasyLinux]
[Linux-Community]
[Ubuntu User]
[Linux Technical Review]
[Linux Magazine]
[Linux Pro Magazine]
[Ubuntu User]
[EasyLinux Poland]
[Linux Magazine Poland]
[Linux Magazine Brasil]
[EasyLinux Brasil]
[Linux Magazine Spain]