Bash/Kontrollstrukturen: Unterschied zwischen den Versionen
K Dirkwagner verschob die Seite Kontrollstrukturen (Bash) nach Bash/Kontrollstrukturen |
|||
(29 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
'''Kontrollstrukturen''' ermöglichen den Ablauf eines Linux-Shell-Skripts zu steuern (Bedingte Verzweigungen, Schleifen, Fallunterscheidungen) | |||
== Bedingungen testen == | == Bedingungen testen == | ||
Zeile 15: | Zeile 11: | ||
In bestimmten Fällen können auf der rechten Seite eines Vergleichs auch Strings oder Zahlen stehen - bei der Ersetzung von leeren Variablen kann es aber zu Syntaxfehlern kommen. Weiterhin lassen sich mehrere Argumente logisch verknüpfen (UND, ODER, NICHT). Beispiel: | In bestimmten Fällen können auf der rechten Seite eines Vergleichs auch Strings oder Zahlen stehen - bei der Ersetzung von leeren Variablen kann es aber zu Syntaxfehlern kommen. Weiterhin lassen sich mehrere Argumente logisch verknüpfen (UND, ODER, NICHT). Beispiel: | ||
test -w /etc/passwd | test -w /etc/passwd | ||
mit der Kommandoverkettung lassen sich so schon logische Entscheidungen treffen, z. B.: | mit der Kommandoverkettung lassen sich so schon logische Entscheidungen treffen, z. B. : | ||
test -w /etc/passwd && echo "Du bist ROOT" | test -w /etc/passwd && echo "Du bist ROOT" | ||
Normalerweise kann statt 'test' das Argument auch in eckigen Klammern gesetzt werden. Die Klammern müssen von Leerzeichen umschlossen werden: | Normalerweise kann statt 'test' das Argument auch in eckigen Klammern gesetzt werden. Die Klammern müssen von Leerzeichen umschlossen werden: | ||
[ -w /etc/passwd ] | [ -w /etc/passwd ] | ||
Die folgenden Operationen können bei 'test' bzw. [ ... ] verwendet werden. | Die folgenden Operationen können bei 'test' bzw. [ ... ] verwendet werden. | ||
{| class="wikitable sortable" | {| class="wikitable sortable" | ||
|- | |- | ||
| colspan="2" | '''Dateitests | | colspan="2" | '''Dateitests ''' | ||
|- | |- | ||
| | <tt>'''-b Datei '''</tt> | | | <tt>'''-b Datei '''</tt> | ||
Zeile 88: | Zeile 81: | ||
|- | |- | ||
|} | |} | ||
'''Beispiel''' | '''Beispiel''' | ||
if [ -d RCS ] Wenn ein Verzeichnis RCS existiert. . . | |||
if [ -d RCS ] Wenn ein Verzeichnis RCS existiert. . . | |||
{| class="wikitable sortable" | {| class="wikitable sortable" | ||
|- | |- | ||
| colspan="2" | '''Bedingungen für Zeichenfolgen | | colspan="2" | '''Bedingungen für Zeichenfolgen ''' | ||
|- | |- | ||
| | <tt>'''-n s1 '''</tt> | | | <tt>'''-n s1 '''</tt> | ||
Zeile 113: | Zeile 106: | ||
|- | |- | ||
|} | |} | ||
'''Beispiel''' | '''Beispiel''' | ||
if [ "$Antwort" != "j" ] Wenn die $Antwort nicht "j" ist. . . | if [ "$Antwort" != "j" ] Wenn die $Antwort nicht "j" ist. . . | ||
Zeile 140: | Zeile 133: | ||
|- | |- | ||
|} | |} | ||
; Beispiele | |||
while test $# -gt 0 Solange Argumente vorliegen. . . | while test $# -gt 0 Solange Argumente vorliegen. . . | ||
Zeile 150: | Zeile 143: | ||
{| class="wikitable sortable" | {| class="wikitable sortable" | ||
|- | |- | ||
| colspan="2" | '''Kombinierte Formen | | colspan="2" | '''Kombinierte Formen ''' | ||
|- | |- | ||
| | <tt>'''(Bedingung) '''</tt> | | | <tt>'''(Bedingung) '''</tt> | ||
Zeile 167: | Zeile 160: | ||
'''Beispiel''' | '''Beispiel''' | ||
if [ ! -r "$1" -o ! -f "$1" ] Wenn das erste Argument keine lesbare oder reguläre Datei ist. | if [ ! -r "$1" -o ! -f "$1" ] Wenn das erste Argument keine lesbare oder reguläre Datei ist. | ||
Zeile 177: | Zeile 169: | ||
* Trotzdem verhät sich eine bedingte Anweisung - oder die Schleifenkonstrukte, die weiter unten behandelt werden - wie eine einzige Anweisung. | * Trotzdem verhät sich eine bedingte Anweisung - oder die Schleifenkonstrukte, die weiter unten behandelt werden - wie eine einzige Anweisung. | ||
* Somit ergibt sich eine starke Ähnlichkeit mit der Blockstruktur von C oder Pascal. | * Somit ergibt sich eine starke Ähnlichkeit mit der Blockstruktur von C oder Pascal. | ||
* Man kann dies ausprobieren, indem man eine if- oder while-Anweisung interaktiv eingibt. Solange nicht 'fi' bzw. 'done' eingetippt wurde, erhält man den PS2-Prompt ('>'). | * Man kann dies ausprobieren, indem man eine if- oder while-Anweisung interaktiv eingibt. Solange nicht 'fi' bzw. 'done' eingetippt wurde, erhält man den PS2-Prompt ('>'). | ||
=== if === | === if === | ||
Die if-Anweisung in der Shell-Programmierung macht das gleiche wie in allen anderen Programmiersprachen, sie testet eine Bedingung auf Wahrheit und macht davon den weiteren Ablauf des Programms abhängig. | * Die if-Anweisung in der Shell-Programmierung macht das gleiche wie in allen anderen Programmiersprachen, sie testet eine Bedingung auf Wahrheit und macht davon den weiteren Ablauf des Programms abhängig. | ||
* Die Syntax der if-Anweisung lautet wie folgt: | |||
Die Syntax der if-Anweisung lautet wie folgt: | |||
if Bedingung1 | if Bedingung1 | ||
then Befehle1 | then Befehle1 | ||
Zeile 192: | Zeile 182: | ||
fi | fi | ||
Wenn die Bedingung1 erfüllt ist, werden die Befehle1 ausgeführt; andernfalls, wenn die Bedingung2 erfüllt ist, werden die Befehle2 ausgeführt. Trifft keine Bedingung zu, sollen die Befehle3 ausgeführt werden. | * Wenn die Bedingung1 erfüllt ist, werden die Befehle1 ausgeführt; andernfalls, wenn die Bedingung2 erfüllt ist, werden die Befehle2 ausgeführt. Trifft keine Bedingung zu, sollen die Befehle3 ausgeführt werden. | ||
* Bedingungen werden normalerweise mit dem Befehl test formuliert. Es kann aber auch der Rückgabewert jedes anderen Kommandos ausgewertet werden. Für Bedingungen, die auf jeden Fall zutreffen sollen steht der Null-Befehl (:) zur Verfügung. | |||
Bedingungen werden normalerweise mit dem Befehl test formuliert. Es kann aber auch der Rückgabewert jedes anderen Kommandos ausgewertet werden. Für Bedingungen, die auf jeden Fall zutreffen sollen steht der Null-Befehl (:) zur Verfügung. | |||
; Beispiele | |||
Man achte auf die Positionierung der Semikoli. | Man achte auf die Positionierung der Semikoli. | ||
#!/bin/sh | #!/bin/sh | ||
# Füge eine 0 vor Zahlen kleiner 10 ein: | # Füge eine 0 vor Zahlen kleiner 10 ein: | ||
Zeile 238: | Zeile 226: | ||
=== Beispiele === | === Beispiele === | ||
Es soll eine Meldung ausgegeben werden, falls mehr als 5 Benutzer eingeloggt sind: | |||
USERS=`who | wc -l` # Zeilen der who-Ausgabe zählen | USERS=`who | wc -l` # Zeilen der who-Ausgabe zählen | ||
if test $USERS -gt 5 | if test $USERS -gt 5 | ||
Zeile 246: | Zeile 233: | ||
fi | fi | ||
* Kürzere Variante | |||
if [ $(who | wc -l) -gt 5 ] ; then | if [ $(who | wc -l) -gt 5 ] ; then | ||
echo "Mehr als 5 Benutzer am Geraet" | echo "Mehr als 5 Benutzer am Geraet" | ||
Zeile 254: | Zeile 240: | ||
Man sollte bei der Entwicklung von Skripts aber ruhig mit der Langfassung beginnen und sich erst der Kurzfassung zuwenden, wenn man mehr Übung hat und die Langfassungen auf Anhieb funktionieren. | Man sollte bei der Entwicklung von Skripts aber ruhig mit der Langfassung beginnen und sich erst der Kurzfassung zuwenden, wenn man mehr Übung hat und die Langfassungen auf Anhieb funktionieren. | ||
* Ein weiteres Beispiel zeigt eine Fehlerprüfung | |||
if test $# -eq 0 | if test $# -eq 0 | ||
Zeile 265: | Zeile 251: | ||
Das nächste Beispiel zeigt eine mehr oder weniger intelligente Anzeige für Dateien und Verzeichnisse. 'show' zeigt bei Dateien den Inhalt mit 'less' an und Verzeichnisse werden mit 'ls' präsentiert. | Das nächste Beispiel zeigt eine mehr oder weniger intelligente Anzeige für Dateien und Verzeichnisse. 'show' zeigt bei Dateien den Inhalt mit 'less' an und Verzeichnisse werden mit 'ls' präsentiert. | ||
* Fehlt der Parameter, wird interaktiv nachgefragt | |||
if [ $# -eq 0 ] # falls keine Angabe | if [ $# -eq 0 ] # falls keine Angabe | ||
then # interaktiv erfragen | then # interaktiv erfragen | ||
Zeile 297: | Zeile 282: | ||
Beim Vergleich von Zeichenketten sollten möglichst die Anführungszeichen (" ... ") verwendet werden, da sonst bei der Ersetzung durch die Shell unvollständige Test-Kommandos entstehen können. | Beim Vergleich von Zeichenketten sollten möglichst die Anführungszeichen (" ... ") verwendet werden, da sonst bei der Ersetzung durch die Shell unvollständige Test-Kommandos entstehen können. | ||
''Beispiel'' | |||
if [ ! -n $1 ] ; then | if [ ! -n $1 ] ; then | ||
echo "Kein Parameter" | echo "Kein Parameter" | ||
Zeile 319: | Zeile 303: | ||
Bei fehlenden Anführungszeichen werden auch führende Leerzeichen der Variablenwerte oder Parameter eliminiert. | Bei fehlenden Anführungszeichen werden auch führende Leerzeichen der Variablenwerte oder Parameter eliminiert. | ||
''Weiteres Beispiel'' | |||
Es kommt ab und zu vor, dass eine Userid wechselt oder dass die Gruppenzugehörigkeit von Dateien geändert werden muss. In solchen fällen helfen die beiden folgenden Skripts: | Es kommt ab und zu vor, dass eine Userid wechselt oder dass die Gruppenzugehörigkeit von Dateien geändert werden muss. In solchen fällen helfen die beiden folgenden Skripts: | ||
#!/bin/sh | #!/bin/sh | ||
# Change user-id | # Change user-id | ||
Zeile 341: | Zeile 324: | ||
== case == | == case == | ||
Auch die case-Anweisung ist vergleichbar in vielen anderen Sprachen vorhanden. | * Auch die case-Anweisung ist vergleichbar in vielen anderen Sprachen vorhanden. | ||
* Sie dient, ähnlich wie die if-Anweisung, zur Fallunterscheidung. Allerdings wird hier nicht nur zwischen zwei Fällen unterschieden (Entweder / Oder), sondern es sind mehrere Fälle möglich. | |||
Sie dient, ähnlich wie die if-Anweisung, zur Fallunterscheidung. Allerdings wird hier nicht nur zwischen zwei Fällen unterschieden (Entweder / Oder), sondern es sind mehrere Fälle möglich. | * Man kann die case-Anweisung auch durch eine geschachtelte if-Anweisung völlig umgehen, allerdings ist sie ein elegantes Mittel um den Code lesbar zu halten. | ||
* Die Syntax der case-Anweisung lautet wie folgt: | |||
Man kann die case-Anweisung auch durch eine geschachtelte if-Anweisung völlig umgehen, allerdings ist sie ein elegantes Mittel um den Code lesbar zu halten. | |||
Die Syntax der case-Anweisung lautet wie folgt: | |||
case Wert in | case Wert in | ||
Muster1) Befehle1;; | Muster1) Befehle1;; | ||
Zeile 357: | Zeile 336: | ||
Wenn der Wert mit dem Muster1 übereinstimmt, wird die entsprechende Befehlsgruppe (Befehle1) ausgeführt, bei Übereinstimmung mit Muster2 werden die Kommandos der zweiten Befehlsgruppe (Befehle2) ausgeführt, usw. | Wenn der Wert mit dem Muster1 übereinstimmt, wird die entsprechende Befehlsgruppe (Befehle1) ausgeführt, bei Übereinstimmung mit Muster2 werden die Kommandos der zweiten Befehlsgruppe (Befehle2) ausgeführt, usw. | ||
* Der letzte Befehl in jeder Gruppe muss mit ;; gekennzeichnet werden. | |||
* Das bedeutet für die Shell soviel wie springe zum nächsten esac, so dass die anderen Bedingungen nicht mehr überprüft werden. | |||
* In den Mustern sind die gleichen Meta-Zeichen erlaubt wie bei der Auswahl von Dateinamen. | |||
* Wenn in einer Zeile mehrere Muster angegeben werden sollen, müssen sie durch ein Pipezeichen (|, logisches ODER) getrennt werden. | |||
; Beispiele | |||
#!/bin/sh | #!/bin/sh | ||
# Mit dem ersten Argument in der Befehlszeile | # Mit dem ersten Argument in der Befehlszeile | ||
Zeile 395: | Zeile 369: | ||
Diese Anweisung erlaubt eine Mehrfachauswahl. Sie wird auch gerne deshalb verwendet, weil sie Muster mit Jokerzeichen und mehrere Muster für eine Auswahl erlauben | Diese Anweisung erlaubt eine Mehrfachauswahl. Sie wird auch gerne deshalb verwendet, weil sie Muster mit Jokerzeichen und mehrere Muster für eine Auswahl erlauben | ||
case selector in | case selector in | ||
Muster-1) Kommandofolge 1 ;; | Muster-1) Kommandofolge 1 ;; | ||
Zeile 406: | Zeile 379: | ||
Die Variable <tt>selector</tt> (String) wird der Reihe nach mit den Mustern "Muster-1" bis "Muster-n" verglichen. Bei Gleichheit wird die nachfolgende Kommandofolge ausgeführt und dann nach der case-Anweisung (also hinter dem <tt>esac</tt>) fortgefahren. * In den Mustern sind Metazeichen (*, ?, []) erlaubt, im Selektor dagegen nicht. | Die Variable <tt>selector</tt> (String) wird der Reihe nach mit den Mustern "Muster-1" bis "Muster-n" verglichen. Bei Gleichheit wird die nachfolgende Kommandofolge ausgeführt und dann nach der case-Anweisung (also hinter dem <tt>esac</tt>) fortgefahren. * In den Mustern sind Metazeichen (*, ?, []) erlaubt, im Selektor dagegen nicht. | ||
* Das Muster * deckt sich mit jedem Selektor --> default-Ausgang. muss als letztes Muster in der case-Konstruktion stehen. | * Das Muster * deckt sich mit jedem Selektor --> default-Ausgang. muss als letztes Muster in der case-Konstruktion stehen. | ||
* Vor der Klammer können mehrere Muster, getrennt durch <tt>| </tt>stehen. | * Vor der Klammer können mehrere Muster, getrennt durch <tt>| </tt>stehen. | ||
'''Das Zeichen <tt>| </tt>bildet eine Oder-Bedingung:''' | |||
case selector in | case selector in | ||
Muster1) Kommandofolge1 ;; | Muster1) Kommandofolge1 ;; | ||
Muster2 | Muster3) Kommandofolge2 ;; | Muster2 | Muster3) Kommandofolge2 ;; | ||
*) Kommandofolge3 ;; | *) Kommandofolge3 ;; | ||
esac | esac | ||
Beispiel 1: Automatische Bearbeitung von Quell- und Objekt-Dateien. Der Aufruf erfolgt mit 'compile Datei'. | |||
case $1 in | case $1 in | ||
Zeile 421: | Zeile 397: | ||
esac | esac | ||
'''Beispiel 2: Menü mit interaktiver Eingabe''' | |||
while : # Endlosschleife (s. später) | while : # Endlosschleife (s. später) | ||
do | do | ||
Zeile 446: | Zeile 421: | ||
== for-Anweisung == | == for-Anweisung == | ||
Diese Schleifenanweisung hat zwei Ausprägungen, mit einer Liste der zu bearbeitenden Elemente oder mit den Kommandozeilenparametern. | * Diese Schleifenanweisung hat zwei Ausprägungen, mit einer Liste der zu bearbeitenden Elemente oder mit den Kommandozeilenparametern. | ||
* Dieses Konstrukt ähnelt nur auf den ersten Blick seinen Pendants aus anderen Programmiersprachen. | |||
Dieses Konstrukt ähnelt nur auf den ersten Blick seinen Pendants aus anderen Programmiersprachen. | * In anderen Sprachen wird die for-Schleife meistens dazu benutzt, eine Zählvariable über einen bestimmten Wertebereich iterieren zu lassen (for i = 1 to 100...next). | ||
* In der Shell dagegen wird die Laufvariable nicht mit aufeinander folgenden Zahlen belegt, sondern mit einzelnen Werten aus einer anzugebenden Liste. | |||
In anderen Sprachen wird die for-Schleife meistens dazu benutzt, eine Zählvariable über einen bestimmten Wertebereich iterieren zu lassen (for i = 1 to 100...next). | * Wenn man eine Laufvariable benötigt, muss man dazu die while-Schleife einsetzen. | ||
In der Shell dagegen wird die Laufvariable nicht mit aufeinander folgenden Zahlen belegt, sondern mit einzelnen Werten aus einer anzugebenden Liste. | |||
Wenn man eine Laufvariable benötigt, muss man dazu die while-Schleife einsetzen. | |||
Die Syntax der for-Schleife lautet wie folgt: | Die Syntax der for-Schleife lautet wie folgt: | ||
Zeile 466: | Zeile 437: | ||
Die Befehle werden ausgeführt, wobei der Variablen x nacheinander die Werte aus der Liste zugewiesen werden. | Die Befehle werden ausgeführt, wobei der Variablen x nacheinander die Werte aus der Liste zugewiesen werden. | ||
* Wie man sieht, ist die Angabe der Liste optional, wenn sie nicht angegeben wird, nimmt x der Reihe nach alle Werte aus $@ (in dieser vordefinierten Variablen liegen die Aufrufparameter) an. | |||
Wie man sieht ist die Angabe der Liste optional, wenn sie nicht angegeben wird, nimmt x der Reihe nach alle Werte aus $@ (in dieser vordefinierten Variablen liegen die Aufrufparameter) an. | * Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden. | ||
Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden. | |||
=== for-Schleife mit Liste === | === for-Schleife mit Liste === | ||
Zeile 479: | Zeile 448: | ||
Die Selektor-Variable wird nacheinander durch die Elemente der Liste ersetzt und die Schleife mit der Selektor-Variablen ausgeführt. | Die Selektor-Variable wird nacheinander durch die Elemente der Liste ersetzt und die Schleife mit der Selektor-Variablen ausgeführt. | ||
; Beispiele | |||
for X in hans heinz karl luise do echo $X done | for X in hans heinz karl luise do echo $X done | ||
Das Programm hat folgende Ausgabe: | Das Programm hat folgende Ausgabe: | ||
hans | hans | ||
heinz | heinz | ||
karl | karl | ||
luise | luise | ||
for FILE in *.txt # drucke alle Textdateien | for FILE in *.txt # drucke alle Textdateien | ||
do # im aktuellen Verzeichnis | do # im aktuellen Verzeichnis | ||
Zeile 497: | Zeile 465: | ||
echo $XX | echo $XX | ||
done | done | ||
Durchsuche Kapitel zur Erstellung einer Wortliste (wie fgrep -f): | |||
for item in $(cat program_list) # cat: Datei ausgeben | for item in $(cat program_list) # cat: Datei ausgeben | ||
do | do | ||
Zeile 512: | Zeile 480: | ||
done | done | ||
Die Selektor-Variable wird nacheinander durch die Parameter $1 bis $n ersetzt und mit diesen Werten die Schleife durchlaufen. Es gibt also $# Schleifendurchläufe. Beispiel | Die Selektor-Variable wird nacheinander durch die Parameter $1 bis $n ersetzt und mit diesen Werten die Schleife durchlaufen. Es gibt also $# Schleifendurchläufe. | ||
'''Beispiel''' | |||
Die Prozedur 'makebak' erzeugt für die in der Parameterliste angegebenen Dateien eine .bak-Datei. | Die Prozedur 'makebak' erzeugt für die in der Parameterliste angegebenen Dateien eine .bak-Datei. | ||
for FF | for FF | ||
do | do | ||
Zeile 535: | Zeile 504: | ||
# sed: Skriptsprache zur Textformatierung | # sed: Skriptsprache zur Textformatierung | ||
mv $file $name | mv $file $name | ||
# mv: Datei verschieben bzw. umbenennen | # mv: Datei verschieben bzw. umbenennen | ||
done | done | ||
Zeile 553: | Zeile 522: | ||
Dabei wird die Bedingung vor der Ausführung der Befehle überprüft. Die Bedingung wird dabei üblicherweise, genau wie bei der if-Anweisung, mit dem Befehl test formuliert. | Dabei wird die Bedingung vor der Ausführung der Befehle überprüft. Die Bedingung wird dabei üblicherweise, genau wie bei der if-Anweisung, mit dem Befehl test formuliert. | ||
Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden. | Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden. | ||
; Beispiele | |||
#!/bin/sh | #!/bin/sh | ||
Zeile 604: | Zeile 573: | ||
Solange der Bedingungsausdruck den Wert 'true' liefert, wird die Schleife ausgeführt. | Solange der Bedingungsausdruck den Wert 'true' liefert, wird die Schleife ausgeführt. | ||
'''Warten auf eine Datei (z. B. vom Hintergrundprozess) ''' | '''Warten auf eine Datei (z. B. vom Hintergrundprozess) ''' | ||
while [ ! -f foo ] | while [ ! -f foo ] | ||
Zeile 661: | Zeile 630: | ||
done | done | ||
Die Schleife wird | Die Schleife wird so lange abgearbeitet, bis Bedingungsausdruck einen Wert ungleich Null liefert. | ||
'''Beispiel''' | |||
# warten auf Datei foo | # warten auf Datei foo | ||
Zeile 689: | Zeile 658: | ||
Die Befehle werden ausgeführt, bis die Bedingung erfüllt ist. Die Bedingung wird dabei üblicherweise, genau wie bei der if-Anweisung, mit dem Befehl test formuliert. | Die Befehle werden ausgeführt, bis die Bedingung erfüllt ist. Die Bedingung wird dabei üblicherweise, genau wie bei der if-Anweisung, mit dem Befehl test formuliert. | ||
Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden. | Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden. | ||
'''Beispiel''' | |||
Hier wird die Bedingung nicht per test, sondern mit dem Rückgabewert des Programms grep formuliert. | Hier wird die Bedingung nicht per test, sondern mit dem Rückgabewert des Programms grep formuliert. | ||
Zeile 704: | Zeile 673: | ||
done | done | ||
echo "Der Meister ist anwesend" | echo "Der Meister ist anwesend" | ||
[[Kategorie:Bash/Scripting]] |
Aktuelle Version vom 8. August 2024, 13:07 Uhr
Kontrollstrukturen ermöglichen den Ablauf eines Linux-Shell-Skripts zu steuern (Bedingte Verzweigungen, Schleifen, Fallunterscheidungen)
Bedingungen testen
Das wichtigste Kommando ist 'test', mit dem man mannigfache Bedingungen testen kann.
test Argument
Dieses Kommando prüft eine Bedingung und liefert 'true' (0), falls die Bedingung erfüllt ist und 'false' (1), falls die Bedingung nicht erfüllt ist. Der Fehlerwert 2 wird zurückgegeben, wenn das Argument syntaktisch falsch ist (meist durch Ersetzung hervorgerufen).
Es lassen sich Dateien, Zeichenketten und Integer-Zahlen (16 Bit, bei Linux 32 Bit) überprüfen. Das Argument von Test besteht aus einer Testoption und einem Operanden, der ein Dateiname oder eine Shell-Variable (Inhalt: String oder Zahl) sein kann.
In bestimmten Fällen können auf der rechten Seite eines Vergleichs auch Strings oder Zahlen stehen - bei der Ersetzung von leeren Variablen kann es aber zu Syntaxfehlern kommen. Weiterhin lassen sich mehrere Argumente logisch verknüpfen (UND, ODER, NICHT). Beispiel:
test -w /etc/passwd
mit der Kommandoverkettung lassen sich so schon logische Entscheidungen treffen, z. B. :
test -w /etc/passwd && echo "Du bist ROOT"
Normalerweise kann statt 'test' das Argument auch in eckigen Klammern gesetzt werden. Die Klammern müssen von Leerzeichen umschlossen werden:
[ -w /etc/passwd ]
Die folgenden Operationen können bei 'test' bzw. [ ... ] verwendet werden.
Dateitests | |
-b Datei | Die Datei existiert und ist ein blockorientiertes Gerät |
-c Datei | Die Datei existiert und ist ein zeichenorientiertes Gerät |
-d Datei | Die Datei existiert und ist ein Verzeichnis |
-e Datei | Datei existiert |
-f Datei | Die Datei existiert und ist eine reguläre Datei |
-g Datei | Die Datei existiert und das Gruppen-ID-Bit ist gesetzt |
-h Datei | Die Datei existiert und ist ein symbolischer Link |
-k Datei | Die Datei existiert und das Sticky-Bit ist gesetzt |
-p Datei | Die Datei existiert und ist eine Named Pipe |
-r Datei | Die Datei existiert und ist lesbar |
-s Datei | Die Datei existiert und ist nicht leer |
-t [n] | DATEI-Descriptor FD (Standard: Standardausgabe) ist auf Tty offen |
-u Datei | Die Datei existiert und das Setuid-Bit ist gesetzt |
-w Datei | Die Datei existiert und ist beschreibbar |
-x Datei | Die Datei existiert und ist ausführbar |
Datei1 -nt Datei2 | Datei1 ist neuer als Datei2 |
Datei1 -ot Datei2 | Datei1 ist älter als Datei2 |
Datei1 -ef Datei2 | Beide Dateien belegen dieselbe Inode auf demselben Gerät |
Beispiel
if [ -d RCS ] Wenn ein Verzeichnis RCS existiert. . .
Bedingungen für Zeichenfolgen | |
-n s1 | Die Länge der Zeichenfolge s1 ist ungleich Null |
-z s1 | Die Länge der Zeichenfolge s1 ist gleich Null |
s1 = s2 | Die Zeichenfolgen s1 und s2 sind identisch |
s1 != s2 | Die Zeichenfolgen s1 und s2 sind nicht identisch |
Zeichenfolge | Die Zeichenfolge ist nicht Null |
Beispiel
if [ "$Antwort" != "j" ] Wenn die $Antwort nicht "j" ist. . .
Ganzzahlvergleiche: | |
n1 -eq n2 | n1 ist gleich n2 |
n1 -ge n2 | n1 ist größer oder gleich n2 |
n1 -gt n2 | n1 ist größer als n2 |
n1 -le n2 | n1 ist kleiner oder gleich n2 |
n1 -lt n2 | n1 ist kleiner n2 |
n1 -ne n2 | n1 ist ungleich n2 |
- Beispiele
while test $# -gt 0 Solange Argumente vorliegen. . . while [ -n "$1" ] Solange das erste Argument nicht leer ist. . . if [ $count -lt 10 ] Wenn $count kleiner 10. . .
Kombinierte Formen | |
(Bedingung) | Wahr, wenn die Bedingung zutrifft (wird für die Gruppierung verwendet). Den Klammern muss ein \ vorangestellt werden. |
! Bedingung i | Wahr, wenn die Bedingung nicht zutrifft (NOT). |
Bedingung1 -a Bedingung2 | Wahr, wenn beide Bedingungen zutreffen (AND). |
Bedingung1 -o Bedingung2 | Wahr, wenn eine der beiden Bedingungen zutrifft (OR). |
Beispiel
if [ ! -r "$1" -o ! -f "$1" ] Wenn das erste Argument keine lesbare oder reguläre Datei ist.
Bedingte Anweisung (if - then - else)
- Wichtig: Als Bedingung kann nicht nur der test-Befehl, sondern eine beliebige Folge von Kommados verwendet werden.
- Jedes Kommando liefert einen Errorcode zurück, der bei erfolgreicher Ausführung gleich Null (true) und bei einem Fehler oder Abbruch ungleich Null (false) ist.
- Zum Testen einer Bedingung dient die if-Anweisung.
- Jede Anweisung muss entweder in einer eigenen Zeile stehen oder durch einen Strichpunkt von den anderen Anweisungen getrennt werden.
- Trotzdem verhät sich eine bedingte Anweisung - oder die Schleifenkonstrukte, die weiter unten behandelt werden - wie eine einzige Anweisung.
- Somit ergibt sich eine starke Ähnlichkeit mit der Blockstruktur von C oder Pascal.
- Man kann dies ausprobieren, indem man eine if- oder while-Anweisung interaktiv eingibt. Solange nicht 'fi' bzw. 'done' eingetippt wurde, erhält man den PS2-Prompt ('>').
if
- Die if-Anweisung in der Shell-Programmierung macht das gleiche wie in allen anderen Programmiersprachen, sie testet eine Bedingung auf Wahrheit und macht davon den weiteren Ablauf des Programms abhängig.
- Die Syntax der if-Anweisung lautet wie folgt:
if Bedingung1 then Befehle1 [ elif Bedingung2 then Befehle2 ] ... [ else Befehle3 ] fi
- Wenn die Bedingung1 erfüllt ist, werden die Befehle1 ausgeführt; andernfalls, wenn die Bedingung2 erfüllt ist, werden die Befehle2 ausgeführt. Trifft keine Bedingung zu, sollen die Befehle3 ausgeführt werden.
- Bedingungen werden normalerweise mit dem Befehl test formuliert. Es kann aber auch der Rückgabewert jedes anderen Kommandos ausgewertet werden. Für Bedingungen, die auf jeden Fall zutreffen sollen steht der Null-Befehl (:) zur Verfügung.
- Beispiele
Man achte auf die Positionierung der Semikoli.
#!/bin/sh # Füge eine 0 vor Zahlen kleiner 10 ein: counter=0 if [ $counter -lt 10 ]; then number=0$counter; else number=$counter; fi #!/bin/bash # Erstelle ein Verzeichnis, wenn es noch nicht existiert: dir=daten if [ ! -e $dir ]; then mkdir $dir; fi # mkdir: Verzeichnis erstellen
einseitiges if
if kommandoliste then kommandos fi
zweiseitiges if
if kommandoliste then kommandos else kommandos fi
Mehrstufiges if
if kommandoliste1 then kommandos elif kommandoliste2 then kommandos elif ... ... fi
Beispiele
Es soll eine Meldung ausgegeben werden, falls mehr als 5 Benutzer eingeloggt sind:
USERS=`who | wc -l` # Zeilen der who-Ausgabe zählen if test $USERS -gt 5 then echo "Mehr als 5 Benutzer am Geraet" fi
- Kürzere Variante
if [ $(who | wc -l) -gt 5 ] ; then echo "Mehr als 5 Benutzer am Geraet" fi
Man sollte bei der Entwicklung von Skripts aber ruhig mit der Langfassung beginnen und sich erst der Kurzfassung zuwenden, wenn man mehr Übung hat und die Langfassungen auf Anhieb funktionieren.
- Ein weiteres Beispiel zeigt eine Fehlerprüfung
if test $# -eq 0 then echo "usage: sortiere filename" >&2 else sort +1 -2 $1 | lp fi
Das nächste Beispiel zeigt eine mehr oder weniger intelligente Anzeige für Dateien und Verzeichnisse. 'show' zeigt bei Dateien den Inhalt mit 'less' an und Verzeichnisse werden mit 'ls' präsentiert.
- Fehlt der Parameter, wird interaktiv nachgefragt
if [ $# -eq 0 ] # falls keine Angabe then # interaktiv erfragen echo -n "Bitte Namen eingeben: " read DATEI else DATEI=$1 fi if [ -f $DATEI ] # wenn normale Datei then # dann ausgeben less $DATEI elif [ -d $DATEI ] # wenn aber Verzeichnis then # dann Dateien zeigen ls -CF $DATEI else # sonst Fehlermeldung echo "cannot show $DATEI" fi
Das nächste Beispiel hängt eine Datei an eine andere Datei an; vorher erfolgt eine Prüfung der Zugriffsberechtigungen:
append Datei1 Datei2
if [ -r $1 -a -w $2 ] then cat $1 >> $2 else echo "cannot append" fi
Beim Vergleich von Zeichenketten sollten möglichst die Anführungszeichen (" ... ") verwendet werden, da sonst bei der Ersetzung durch die Shell unvollständige Test-Kommandos entstehen können.
Beispiel
if [ ! -n $1 ] ; then echo "Kein Parameter" fi
Ist $1 wirklich nicht angegeben, wird das Kommando reduziert zu:
if [ ! -n ] ; then ....
Es ist also unvollständig und es erfolgt eine Fehlermeldung. Dagegen liefert
if [ ! -n "$1" ] ; then echo "Kein Parameter" fi
bei fehlendem Parameter den korrekten Befehl
if [ ! -n "" ]
Bei fehlenden Anführungszeichen werden auch führende Leerzeichen der Variablenwerte oder Parameter eliminiert.
Weiteres Beispiel
Es kommt ab und zu vor, dass eine Userid wechselt oder dass die Gruppenzugehörigkeit von Dateien geändert werden muss. In solchen fällen helfen die beiden folgenden Skripts:
#!/bin/sh # Change user-id # if [ $# -ne 2 ] ; then echo "usage `basename $0` <old id> <new id>" exit fi find ~ -user $1 -exec chown $2 {} ";" #!/bin/sh # Change group-id # if [ $# -ne 2 ] ; then echo "usage `basename $0` <old id> <new id>" exit fi find / -group $1 -exec chgrp $2 {} ";"
case
- Auch die case-Anweisung ist vergleichbar in vielen anderen Sprachen vorhanden.
- Sie dient, ähnlich wie die if-Anweisung, zur Fallunterscheidung. Allerdings wird hier nicht nur zwischen zwei Fällen unterschieden (Entweder / Oder), sondern es sind mehrere Fälle möglich.
- Man kann die case-Anweisung auch durch eine geschachtelte if-Anweisung völlig umgehen, allerdings ist sie ein elegantes Mittel um den Code lesbar zu halten.
- Die Syntax der case-Anweisung lautet wie folgt:
case Wert in Muster1) Befehle1;; Muster2) Befehle2;; ... esac
Wenn der Wert mit dem Muster1 übereinstimmt, wird die entsprechende Befehlsgruppe (Befehle1) ausgeführt, bei Übereinstimmung mit Muster2 werden die Kommandos der zweiten Befehlsgruppe (Befehle2) ausgeführt, usw.
- Der letzte Befehl in jeder Gruppe muss mit ;; gekennzeichnet werden.
- Das bedeutet für die Shell soviel wie springe zum nächsten esac, so dass die anderen Bedingungen nicht mehr überprüft werden.
- In den Mustern sind die gleichen Meta-Zeichen erlaubt wie bei der Auswahl von Dateinamen.
- Wenn in einer Zeile mehrere Muster angegeben werden sollen, müssen sie durch ein Pipezeichen (|, logisches ODER) getrennt werden.
- Beispiele
#!/bin/sh # Mit dem ersten Argument in der Befehlszeile # wird die entsprechende Aktion festgelegt: case $1 in # nimmt das erste Argument Ja|Nein) response=1;; *) echo "Unbekannte Option"; exit 1;; esac
#!/bin/sh # Lies die Zeilen von der Standardeingabe, bis eine # Zeile mit einem einzelnen Punkt eingegeben wird: while : # Null-Befehl do echo -e "Zum Beenden . eingeben ==> \c" read line # read: Zeile von StdIn einlesen case "$line" in .) echo "Ausgefuehrt" break;; *) echo "$line" >>./message ;; esac done
case-Anweisung
Diese Anweisung erlaubt eine Mehrfachauswahl. Sie wird auch gerne deshalb verwendet, weil sie Muster mit Jokerzeichen und mehrere Muster für eine Auswahl erlauben
case selector in Muster-1) Kommandofolge 1 ;; Muster-2) Kommandofolge 2 ;; .... Muster-n) Kommandofolge n ;; esac
Die Variable selector (String) wird der Reihe nach mit den Mustern "Muster-1" bis "Muster-n" verglichen. Bei Gleichheit wird die nachfolgende Kommandofolge ausgeführt und dann nach der case-Anweisung (also hinter dem esac) fortgefahren. * In den Mustern sind Metazeichen (*, ?, []) erlaubt, im Selektor dagegen nicht.
- Das Muster * deckt sich mit jedem Selektor --> default-Ausgang. muss als letztes Muster in der case-Konstruktion stehen.
- Vor der Klammer können mehrere Muster, getrennt durch | stehen.
Das Zeichen | bildet eine Oder-Bedingung:
case selector in Muster1) Kommandofolge1 ;; Muster2 | Muster3) Kommandofolge2 ;; *) Kommandofolge3 ;; esac
Beispiel 1: Automatische Bearbeitung von Quell- und Objekt-Dateien. Der Aufruf erfolgt mit 'compile Datei'.
case $1 in *.s) as $1 ;; # Assembler aufrufen *.c) cc -c $1 ;; # C-Compiler aufrufen *.o) cc $1 -o prog ;; # C-Compiler als Linker *) echo "invalid parameter: $1";; esac
Beispiel 2: Menü mit interaktiver Eingabe
while : # Endlosschleife (s. später) do tput clear # Schirm löschen und Menütext ausgeben echo " +---------------------------------+" echo " | 0 --> Ende |" echo " | 1 --> Datum und Uhrzeit |" echo " | 2 --> aktuelles Verzeichnis |" echo " | 3 --> Inhaltsverzeichnis |" echo " | 4 --> Mail |" echo "+----------------------------------+" echo "Eingabe: \c" # kein Zeilenvorschub read ANTW case $ANTW in 0) kill -9 0 ;; # und tschuess 1) date ;; 2) pwd ;; 3) ls -CF ;; 4) elm ;; *) echo "Falsche Eingabe!" ;; esac done
for-Anweisung
- Diese Schleifenanweisung hat zwei Ausprägungen, mit einer Liste der zu bearbeitenden Elemente oder mit den Kommandozeilenparametern.
- Dieses Konstrukt ähnelt nur auf den ersten Blick seinen Pendants aus anderen Programmiersprachen.
- In anderen Sprachen wird die for-Schleife meistens dazu benutzt, eine Zählvariable über einen bestimmten Wertebereich iterieren zu lassen (for i = 1 to 100...next).
- In der Shell dagegen wird die Laufvariable nicht mit aufeinander folgenden Zahlen belegt, sondern mit einzelnen Werten aus einer anzugebenden Liste.
- Wenn man eine Laufvariable benötigt, muss man dazu die while-Schleife einsetzen.
Die Syntax der for-Schleife lautet wie folgt:
for x [ in Liste ] do Befehle done
Die Befehle werden ausgeführt, wobei der Variablen x nacheinander die Werte aus der Liste zugewiesen werden.
- Wie man sieht, ist die Angabe der Liste optional, wenn sie nicht angegeben wird, nimmt x der Reihe nach alle Werte aus $@ (in dieser vordefinierten Variablen liegen die Aufrufparameter) an.
- Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden.
for-Schleife mit Liste
for selector in liste do Kommandofolge done
Die Selektor-Variable wird nacheinander durch die Elemente der Liste ersetzt und die Schleife mit der Selektor-Variablen ausgeführt.
- Beispiele
for X in hans heinz karl luise do echo $X done
Das Programm hat folgende Ausgabe:
hans heinz karl luise
for FILE in *.txt # drucke alle Textdateien do # im aktuellen Verzeichnis lpr $FILE done for XX in $VAR # geht auch mit do echo $XX done
Durchsuche Kapitel zur Erstellung einer Wortliste (wie fgrep -f):
for item in $(cat program_list) # cat: Datei ausgeben do echo "Pruefung der Kapitel auf" echo "Referenzen zum Programm $item ..." grep -c "$item.[co]" chap* # grep: nach Muster suchen done
for-Schleife mit Parametern
for selector do Kommandofolge done
Die Selektor-Variable wird nacheinander durch die Parameter $1 bis $n ersetzt und mit diesen Werten die Schleife durchlaufen. Es gibt also $# Schleifendurchläufe.
Beispiel
Die Prozedur 'makebak' erzeugt für die in der Parameterliste angegebenen Dateien eine .bak-Datei.
for FF do cp $FF ${FF}.bak done #!/bin/sh # Seitenweises Formatieren der Dateien, die auf der # Befehlszeile angegeben wurden, und speichern des # jeweiligen Ergebnisses: for file do pr $file > $file.tmp # pr: Formatiert Textdateien done # Ermittle einen Ein-Wort-Titel aus jeder Datei und # verwende ihn als neuen Dateinamen: for file do name=`sed -n 's/NAME: //p' $file` # sed: Skriptsprache zur Textformatierung mv $file $name # mv: Datei verschieben bzw. umbenennen done
while
Die while-Schleife ist wieder ein Konstrukt, das einem aus vielen anderen Sprachen bekannt ist:
Die kopfgesteuerte Schleife. Die Syntax der while-Schleife lautet wie folgt:
while Bedingung do Befehle done
Die Befehle werden so lange ausgeführt, wie die Bedingung erfüllt ist.
Dabei wird die Bedingung vor der Ausführung der Befehle überprüft. Die Bedingung wird dabei üblicherweise, genau wie bei der if-Anweisung, mit dem Befehl test formuliert.
Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden.
- Beispiele
#!/bin/sh # Zeilenweise Ausgabe aller Aufrufparameter: while [ -n "$1"]; do echo $1 shift # mit shift werden die Parameter nach # Links geshiftet (aus $2 wird $1) done
Zählschleife
In anderen Sprachen kann man mit der for-Schleife eine Zählvariable über einen bestimmten Wertebereich iterieren lassen (for i = 1 to 100...next).
Da das mit der for-Schleife der Shell nicht geht, ersetzt man die Funktion durch geschickte Anwendung der while-Schleife:
#!/bin/sh # Ausgabe der Zahlen von 1 bis 100: i=1 while [ $i -le 100 ] do echo $i i=`expr $i + 1` done
Weitere Beispiele
#!/bin/sh while who | grep "^root " do sleep 30 done echo Die Katze ist aus dem Haus, Zeit, dass die Mäuse tanzen!
Ergebniswert eines Kommandos
Als Bedingung kann nicht nur eine "klassische" Bedingung (test oder [ ]) sondern auch der Ergebniswert eines Kommandos oder einer Kommandofolge verwendet werden.
while Bedingung do Kommandofolge done
Solange der Bedingungsausdruck den Wert 'true' liefert, wird die Schleife ausgeführt.
Warten auf eine Datei (z. B. vom Hintergrundprozess)
while [ ! -f foo ] do sleep 10 # Wichtig damit die Prozesslast nicht zu hoch wird done
Pausenfüller für das Terminal Abbruch mit DEL-Taste
while : do tput clear # BS löschen echo -e "\n\n\n\n\n" # 5 Leerzeilen banner $(date '+ %T ') # Uhrzeit groß sleep 10 # 10s Pause done
Umbenennen von Dateien durch Anhängen eines Suffix
# Aufruf change suffix datei(en) if [ $# -lt 2 ] ; then echo "Usage: `basename $0` suffix file(s)" else SUFF=$1 # Suffix speichern shift while [ $# -ne 0 ] # solange Parameter da sind do mv $1 ${1}.$SUFF # umbenennen shift done fi
Umbenennen von Dateien durch Anhängen eines Suffix Variante 2 mit for
# Aufruf change suffix datei(en) if [ $# -lt 2 ] ; then echo "Usage: `basename $0` suffix file(s)" else SUFF=$1 # Suffix speichern shift for FILE do mv $FILE ${FILE}.$SUFF # umbenennen shift done fi
until-Anweisung
Diese Anweisung ist identisch zu einer while-Schleife mit negierter Bedingung.
Als Bedingung kann nicht nur eine "klassische" Bedingung (test oder [ ]) sondern auch der Ergebniswert eines Kommandos oder einer Kommandofolge verwendet werden.
until Bedingung do Kommandofolge done
Die Schleife wird so lange abgearbeitet, bis Bedingungsausdruck einen Wert ungleich Null liefert.
Beispiel
# warten auf Datei foo until [ -f foo ] do sleep 10 done
Warten auf einen Benutzer
# warten, bis sich der Benutzer hans eingeloggt hat TT=`who | grep -c "hans"` until [ $TT -gt 0 ] do sleep 10 TT=`who | grep -c "hans"` done # warten, bis sich der Benutzer hans eingeloggt hat # Variante 2 – kuerzer until [ `who | grep -c "hans"` -gt 0 ] do sleep 10 done
Die Befehle werden ausgeführt, bis die Bedingung erfüllt ist. Die Bedingung wird dabei üblicherweise, genau wie bei der if-Anweisung, mit dem Befehl test formuliert.
Wenn die Ausführung eines Schleifendurchlaufs bzw. der ganzen Schleife abgebrochen werden soll, müssen die Kommandos continue bzw. break benutzt werden.
Beispiel
Hier wird die Bedingung nicht per test, sondern mit dem Rückgabewert des Programms grep formuliert.
#!/bin/sh # Warten, bis sich der Administrator einloggt: until who | grep "root"; do # who: Liste der Benutzer # grep: Suchen nach Muster sleep 30 # sleep: warten done echo "Der Meister ist anwesend"