Beschreibung: Prüfungskandidaten sollten in der Lage sein, existierende Scripts anzupassen und einfache neue (ba)sh Scripts zu schreiben. Dieses Lernziel beinhaltet die Verwendung der Standard sh Syntax (Schleifen, Tests), das Verwenden von Kommandosubstitution, das Prüfen von Kommando-Rückgabewerten, Testen des Status einer Datei und Schicken von Mails an den Superuser unter bestimmten Bedingungen. Ebenfalls enthalten ist die Vorsorge, daß der korrekte Interpreter auf der ersten Zeile (#!) von Scripts aufgerufen wird. Weiters enthalten ist die Verwaltung von Speicherort, Eigentum und Ausführungs- und suid-Rechten von Scripts.
Die wichtigsten Dateien, Bezeichnungen und Anwendungen:
Dabei gehen die Möglichkeiten der Shell aber weit über die Batch-Programmierung von DOS hinaus. Sie bietet echte Schleifen, vernünftige Ein/Ausgabe, flexible Variablen, Bedingungsüberprüfung und vieles mehr. Es ist sogar möglich, Funktionen zu nutzen und so genauso strukturiert zu arbeiten, wie es in modernen Hochsprachen üblich ist.
Shellscripts werden in der Systemverwaltung sehr gerne und häufig eingesetzt, weil sie sehr schnell und effizient Lösungen für häufig vorkommende Probleme ermöglichen. Das Programmieren der Shell ist aber eben eine richtige kleine Programmiersprache, es würde den Rahmen des Kurses sicher sprengen, wenn das hier ein kompletter Programmierkurs wäre. Im Folgenden werden alle wichtigen Elemente der Shellscripts beschrieben, die sichere Anwendung kommt aber sicher nur mit viel Praxis...
bash script1Das ist aber natürlich nicht die eleganteste Lösung, schöner wäre es ja, das Script direkt wie ein Programm aufrufen zu können. Dazu müssen wir dem Script einfach nur das Ausführungsrecht geben.
chmod +x script1Wenn wir uns jetzt das Script mit ls -l ansehen, so hat es jetzt neben den bisherigen Zugriffsrechten auch das x-Recht. Es ist jetzt durch die Nennung seines Namens aufrufbar. (Näheres zu chmod auf der entsprechenden Handbuchseite und im Abschnitt 1.104.5 der Vorbereitung auf die LPI101 Prüfung.)
Das geht solange gut, solange wir immer mit der selben Shell arbeiten oder das Script keine Elemente enthält, die eine andere Shell nicht verstehen würde. Aber sobald wir in unserem Script ein paar bash-Spezialitäten einbauen würden und dann plötzlich mit der C-Shell arbeiten würden fielen wir auf die Nase.
Das Problem ist also, daß unser Script selbst noch keine Angaben enthält, von welcher Shell es ausgeführt werden soll. Aber keine Angst, auch hierfür gibt es eine einfache Lösung. Jede Shell unter jedem Unix versteht eine ganz spezielle Kommentarzeile.
Wenn ein Script in der ersten Zeile die Anweisung
#!Programmaufweist, so startet jede Shell das Progamm und gibt ihm als Parameter den Namen des Scripts mit. Wenn wir also in Zukunft grundsätzlich als erste Zeile unserer Scripts schreiben:
#!/bin/bashsind wir auf der sicheren Seite. Selbst wenn wir mit der C-Shell oder einem ganz anderen Kommandointerpreter arbeiten wird für die Abarbeitung unseres Scripts jetzt grundsätzlich die bash benutzt.
Ein häufiger Fehler ist es, daß ein Schreibfehler in diese erste Zeile gemacht wird. Wenn dann das Script ausgeführt werden soll, dann findet die Shell den Interpreter nicht, da der ja falsch geschrieben ist. Also bringt sie eine etwas mißverständliche Fehlermeldung:
bash: Scriptname: No such file or directoryDas könnte man jetzt mißverstehen und denken, die Shell hätte das Script nicht gefunden. In Wahrheit hat sie den Interpreter nicht gefunden...
Zunächst noch einmal die Erinnerung, was sind Kommandozeilenparameter? Wenn wir ein Script schreiben, das z.B. addiere heißt und das zwei Zahlen addieren soll, dann werden wir vom Anwender des Scripts erwarten, daß er die beiden zu addierenden Zahlen als Parameter übergibt, daß er also z.B. schreibt:
addiere 10 20In diesem Fall wäre der Kommandozeilenparameter Nummer 1 also die 10, der Parameter2 die 20. Innerhalb der Shell können diese Parameter angesprochen werden mit $1 und $2. Unser Script könnte also folgendermaßen aussehen:
#!/bin/bash let ergebnis=$1+$2 echo $1 plus $2 ergibt $ergebnis
Script | Parameter1 | Parameter2 | Parameter3 | Parameter4 | ... |
$0 | $1 | $2 | $3 | $4 | ... |
#!/bin/bash cp $*Egal, wieviele Parameter jetzt angegeben wurden, alle werden einfach durch das $* übermittelt.
Sehr häufig ist es notwendig zu erfahren, wieviele Parameter überhaupt angegeben wurden. Dazu dient die spezielle Variable $#.
Zusammenfassend existieren also folgende spezielle Variablen für die Kommandozeilenparameter:
${n} | Der nte Parameter bei mehr als 9 Parametern (nur bash) | |
---|---|---|
$@ | Steht für alle angegebenen Parameter | |
$* | Steht für alle angegebenen Parameter | |
$# | Steht für die Anzahl aller angegebenen Parameter |
Der Unterschied zwischen $* und $@ ist marginal und wird sehr selten gebraucht. Er bezieht sich nur auf die Frage, wie die Shell reagiert, wenn $* oder $@ in doppelten Anführungszeichen stehen.
Das | wird ersetzt durch | |
---|---|---|
"$*" | "$1 $2 $3 ..." | |
"$@" | "$1" "$2" "$3" "..." |
Auch wenn die Schleifenkonstruktion noch unbekannt ist folgt hier ein Beispiel. Das folgende Script gibt alle eingegebenen Parameter untereinander aus:
#!/bin/bash while [ $# -gt 0 ] #Solange die Anzahl der Parameter ($#) größer 0 do echo $1 #Ausgabe des ersten Parameters shift #Parameter verschieben $2->$1, $3->$2, $4->$3,... done
1=Hansum damit $1 den Wert Hans zu geben. Falls in seltenen Fällen es doch notwendig sein sollte, die Kommandozeilenparameter zu ändern, so kann das mit dem Befehl set erledigt werden. Allerdings können nur alle Parameter gleichzeitig verändert werden. set schreibt einfach die gesamte Parameterkette neu.
Das heißt, alle Parameter, die dem Befehl set übergeben werden, werden so behandelt, als wären sie dem Script übergeben werden. Die echten Parameter, die dem Script wirklich übergeben wurden fallen dadurch alle weg, auch wenn set weniger Parameter erhält, als dem Script mitgegeben wurden.
Diese Anwendung von set macht zum Beispiel Sinn, wenn wir ein Script schreiben wollen, das zwingend zwei Kommandoparameter braucht. Wenn wir am Anfang die Anzahl der Parameter überprüfen und merken, daß das Script keine Parameter bekommen hat, so können wir mit set voreingestellte Parameter definieren.
if Kommando then Aktion Aktion ... fiNatürlich sind die Aktionen auch wieder normale Unix-Befehle. Das "fi", das den Block beendet, der durch "if ... then" begonnen wurde, ist einfach nur das "if" rückwärts geschrieben.
Damit wir nicht jedesmal schreiben müssen
if test ...gibt es einen symbolischen Link auf das Programm test, der einfach [ heißt. Allerdings verlangt das Programm test, wenn es merkt, daß es als [ aufgerufen wurde, auch als letzten Parameter eine eckige Klammer zu. Damit ist es also möglich zu schreiben:
if [ ... ]Wichtig ist hierbei, daß unbedingt ein Leerzeichen zwischen if und der Klammer und zwischen der Klammer und den eigentlichen Tests stehen muß. Es handelt sich bei der Klammer ja tatsächlich um einen Programmaufruf!
Die verschiedenen Bedingungsüberprüfungen mit test bzw. [
Mit diesen Tests sind so ziemlich alle denkbaren Bedingungsüberprüfungen möglich, die in einem Shellscript notwendig sind.
if [ Ausdruck ] then Kommandos else Kommandos fi
if [ Ausdruck ] then Kommandos elif [ Ausdruck ] then Kommandos else Kommandos fi
case Variable in Muster1) Kommando1 ;; Muster2) Kommando2 ;; Muster3) Kommando3 ;; ... esacZu beachten sind zunächst die Klammern, die das Muster abschließen. Das Kommando, das zum jeweiligen Muster passt muß mit zwei Strichpunkten abgeschlossen werden. Statt einem Kommando können nämlich auch mehrere Kommandos, durch Strichpunkt getrennt stehen. Nur die doppelten Strichpunkte machen der Shell klar, daß das Kommando für den bestimmten Fall jetzt fertig ist.
Der Abschluß mit esac ist wieder einfach das Wort case rückwärts geschrieben.
Die Shell bietet zwei Formen der Schleifen. die Kopfgesteuerte Schleife und die for-Schleife, die eine Liste abarbeitet.
Als Bedingung wird wieder jedes Programm akzeptiert, das einen Rückgabewert liefert. Ein Rückgabewert von 0 bedeutet, die Bedingung ist wahr, jeder andere bedeutet unwahr. Wie bei der if-Anweisung wird hier meistens wieder das Programm test oder eben dessen abgewandelte Form [ benutzt. Die verschiedenen Überprüfungen wurden bei der if-Anweisung detailiert dargestellt.
Die prinzipielle Form der while-Schleife mit dem [-Programm als Bedingungsüberprüfung sieht dann so aus:
while [ Ausdruck ] do Kommandos... done
for Variable in Liste do Kommandos... doneAls Liste gilt dabei jede mit Leerzeichen, Tabs oder Zeilentrennern getrennte Liste von Worten. Damit diese Funktionsweise etwas klarer wird ein einfaches Beispiel:
#!/bin/bash for i in Hans Peter Otto do echo $i doneDiese Schleife würde also dreimal durchlaufen. Im ersten Durchgang bekommt die Variable i den Wert "Hans", im zweiten "Peter" und im dritten "Otto". Die Ausgabe der Schleife wäre also einfach
Hans Peter OttoRichtig interessant wird diese Schleife jedoch, wenn als Liste ein Jokerzeichenkonstrukt steht wie etwa *.txt - Die Shell löst dieses Konstrukt ja in eine Liste aller Dateien im aktuellen Verzeichnis auf, die auf das Muster passen. Die Schleife wird also sooft durchlaufen, wie es Dateien gibt, die die Endung .txt vorweisen...
Eine andere häufige Form der Anwendung ist die Abarbeitung aller Kommandozeilenparameter eines Shellscripts. Die Variable $@ bietet ja diese Parameter alle zusammen. Diese Schleife wird so oft benutzt, daß es dafür eine Sonderform gibt, statt zu schreiben:
for name in $@ do ... donereicht es zu schreiben
for name do ... doneEine große Stärke der for-Schleife ist es, als Liste die Ergebnisse eines Unix-Befehls einzugeben. Mit Hilfe der Kommandosubstitution ist es ohne weiteres möglich, die komplexesten Unix-Befehle einzugeben, die als Ergebnis eine Liste ausgeben und diese Liste dann in der Schleife zu verarbeiten.
So kann man beispielsweise eine Liste aller User, die dem System bekannt sind dadurch erhalten, daß man mit dem Befehl cut die erste Spalte der Datei /etc/passwd ausschneidet. Der Befehl würde folgendermaßen aussehen:
cut -d: -f1 /etc/passwdUm die Liste, die dieser Befehl ausgibt, als Liste für die for-Schleife zu nutzen muß der Befehl ja nur in Grave-Zeichen gesetzt werden, also
#!/bin/bash for i in `cut -d: -f1 /etc/passwd` do echo $i done
Die Syntax von Funktionen wurden bereits im letzten Kapitel beschrieben, hir nur noch ein paar Anwendungsbeispiele im Script.
Als (zweifellos sinnloses) Beispiel folgt hier ein kleines Script, das eine Funktion enthält, die von einem bestimmten Startwert zu einem bestimmten Endwert zählt. Start- und Endwert werden im Hauptprogramm erfragt, die eigentliche Aufgabe des Zählens erledigt die Funktion:
#!/bin/bash function zaehle_von_bis() { i=$1 # i bekommt den Wert des ersten Parameters while [ $i -le $2 ] # Solange i kleiner/gleich Parameter2 do echo $i # Ausgabe der Zahl i let i=$i+1 # i wird um 1 erhöht done } # Das Hauptprogramm read -p "Startwert: " zahl1 # Startwert einlesen read -p "Endwert: " zahl2 # Endwert einlesen zaehle_von_bis $zahl1 $zahl2 # Aufruf der Funktion mit den gelesenen Werten
Das Programm test beispielsweise nutzt diese Fähigkeit, um bestimmte Tests durchzuführen und die Ergebnisse als Rückgabewert (0 bedeutet Test war erfolgreich also wahr) zurückzuliefern.
Die Shell kennt drei Methoden, diesen Rückgabewert zu analysieren:
... Programm if [ $? -eq 0 ] then ... # Programm war erfolgreich fiSobald ein anderes Programm abgelaufen ist, hat die Variable $? aber bereits einen anderen Wert, den des neuen Programms eben.
Eine häufig benutzte Zeile, die die dritte Möglichkeit ausnutzt ist
test -x /usr/bin/foo || exitWenn das Programm /usr/bin/foo nicht existiert und ausführbar ist, wird das Script mit exit beendet.
Dieser Befehl schickt eine Mail mit bestimmter Titelzeile an eine bestimmte Adresse. Die Mail selbst wird entweder aus einer Datei oder aus einem Here-Dokument von der Standard-Eingabe gelesen. Im Script ist die Form des Here-Documents am verbreitetsten:
mail -s Titel Adresse <<EOM Beliebige Textzeilen EOMAlles, was zwischen den beiden EOMs steht, wird an die angegebene Adresse per Mail verschickt. Beinhaltet der Titel Leerzeichen, so muß er in Anführungszeichen gesetzt werden. Als Adresse für den Systemverwalter kann einfach root eingegeben werden:
#!/bin/bash Auslastung=`df /dev/hda1| grep ^/ |cut -c52-54` if [ $Auslastung -gt 90 ] then mail -s "Alarm: Platte bald voll" root <<EOM Hallo Systemverwalter. Die Platte /dev/hda1 ist bald voll. Sie ist zu $Auslastung belegt. Mach was!!! EOM fiEs können aber auch Programme direkt ihre Ausgaben an mail weiterpipen, also etwa
df | mail -s "Ausgabe von df" rootIn beiden Fällen wird eine Mail an root verschickt, im ersten Beispiel, wird eine Warnung an root weitergegeben, wenn die Platte /dev/hda1 voller als 90% ist, im zweiten wird einfach die Ausgabe von df gemailt.
Soll nur der Systemverwalter diese Scripts ausführen dürfen, dann empfiehlt es sich,
Grundsätzliche Vorsicht ist mit der Verwendung des SUID-Bits angeraten. Die Shell reagiert aber seit einigen Jahren sehr vorsichtig darauf und bezieht sich ihre Informationen über die Identität nicht aus der Effektiven UID sondern aus der echten UID. Ältere Versionen können hier aber erhebliche Schwierigkeiten machen...