Zum Inhalt springen

Bash/Kontrollstrukturen

Aus Foxwiki

Bash/Kontrollstrukturen - Ablauf eines Linux-Shell-Skripts steuern

Beschreibung

Bedingte Verzweigungen, Schleifen, Fallunterscheidungen

Bedingungen

Bedingungen testen

siehe test

if - then - else

Als Bedingung kann nicht nur der test-Befehl, sondern eine beliebige Folge von Kommandos 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
Testen einer Bedingung mit der if-Anweisung

Jede Anweisung muss entweder in einer eigenen Zeile stehen oder durch einen Strichpunkt von den anderen Anweisungen getrennt werden

  • Trotzdem verhält sich eine bedingte Anweisung - oder die Schleifenkonstrukte, die weiter unten behandelt werden - wie eine einzige Anweisung
  • 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 ('>')

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

Syntax
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 [ ! -edir ]; then
mkdirdir; 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 -21 | lp
fi
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
lessDATEI
elif [ -d DATEI ] # wenn aber Verzeichnis
then # dann Dateien zeigen
ls -CF DATEI
else # sonst Fehlermeldung
echo "cannot showDATEI"
fi
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 vor, dass eine Userid wechselt oder die Gruppenzugehörigkeit von Dateien geändert werden muss. In solchen Fällen helfen die beiden folgenden Skripte

#!/bin/sh
# Change user-id
#
if [# -ne 2 ] ; then
echo "usage `basename0` <old id> <new id>"
exit
fi
find ~ -user1 -exec chown2 {} ";"
#!/bin/sh
# Change group-id
#
if [# -ne 2 ] ; then
echo "usage `basename0` <old id> <new id>"
exit
fi
find / -group1 -exec chgrp2 {} ";"

Fallunterscheidung

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, und weitere

  • 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'

case1 in
*.s) as1 ;; # Assembler aufrufen
*.c) cc -c1 ;; # C-Compiler aufrufen
*.o) cc1 -o prog ;; # C-Compiler als Linker
*) echo "invalid parameter:1";;
esac
Beispiel 2

Menü mit interaktiver Eingabe

while : # Endlosschleife
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

Schleifen

for-Anweisung

Diese Schleifenanweisung hat zwei Ausprägungen

  • mit einer Liste der zu bearbeitenden Elemente
  • 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

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 Programmitem ..."
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 Parameter1 bisn 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
cpFF{FF}.bak
done
#!/bin/sh
# Seitenweises Formatieren der Dateien, die auf der
# Befehlszeile angegeben wurden, und speichern des
# jeweiligen Ergebnisses

for file do
prfile >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
mvfilename
# mv: Datei verschieben bzw.&nbsp;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 (aus2 wird1)
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
echoi
i=`expri + 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 (beispielsweise 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: `basename0` suffix file(s)"
else
SUFF=$1 # Suffix speichern
shift
while [# -ne 0 ] # solange Parameter da sind
do
mv1{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: `basename0` suffix file(s)"
else
SUFF=$1 # Suffix speichern
shift
for FILE
do
mvFILE{FILE}.$SUFF # umbenennen
shift
done
fi

until

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"