Kontrollstrukturen
Ein Skript, das immer dasselbe tut, ist eine Befehlsliste. Interessant wird es, wenn das Skript auf Eingaben reagiert, Entscheidungen trifft und Dinge wiederholt. Dafür gibt es Kontrollstrukturen. Du lernst sie an einem Menü-Skript kennen, das am Ende dein addscript.sh bedient.
Das Gerüst erzeugen
Abschnitt betitelt „Das Gerüst erzeugen“Das zahlt sich jetzt aus: Das Gerüst des neuen Skripts erzeugst du mit addscript.sh aus Teil 1.
cd labs/linux/08-skripte./addscript.sh menu.sh "Interaktives Menü"Der Editor öffnet sich mit dem fertigen Kopf. Ergänze das Hauptprogramm um die Ausgabe des Menüs:
Listing 1: Das Menü ausgeben
Abschnitt betitelt „Listing 1: Das Menü ausgeben“#!/bin/bash## menu.sh# Interaktives Menü## 2026-11-05 / Vorname Nachname#
# Variablen
# Funktionen
# Hauptprogramm
# Menü ausgebenecho "==== Skript-Menü ==== 1 Alle Shell-Skripte auflisten 2 Neues Shell-Skript erstellen e Ende"Testen. ./menu.sh sollte das Menü anzeigen. Mehr noch nicht.
Eingaben lesen: read
Abschnitt betitelt „Eingaben lesen: read“Das Menü ist bisher reine Dekoration. Eingaben liest read und legt sie in einer Variablen ab. Du kennst den Befehl aus Aufgabe 8.1:
Listing 2: Eingabe entgegennehmen
Abschnitt betitelt „Listing 2: Eingabe entgegennehmen“# Menü ausgebenecho "==== Skript-Menü ==== 1 Alle Shell-Skripte auflisten 2 Neues Shell-Skript erstellen e Ende"
# Eingabe einlesenread -r -p "Auswahl eingeben, mit Enter bestätigen: " antwortecho "Du hast '$antwort' gewählt."-p gibt die Eingabeaufforderung aus und lässt den Cursor dahinter stehen, -r verhindert, dass Backslashes in der Eingabe umgedeutet werden. Die letzte Zeile ist eine Kontrollausgabe. Testen: Gib eine Zahl ein, einen Buchstaben, nichts, einen Satz mit Leerzeichen.
Endlosschleife: while
Abschnitt betitelt „Endlosschleife: while“Nach einer Eingabe ist das Skript fertig. Ein Menü soll aber so lange anzeigen und fragen, bis die Person es beendet. Das ist eine Schleife mit Abbruchbedingung:
flowchart TD
S(["Start"]) --> M["Menü ausgeben"]
M --> R["Eingabe einlesen"]
R --> Q{"Eingabe = e?"}
Q -->|nein| M
Q -->|ja| E(["Ende"])
Die while-Schleife wiederholt ihren Block, solange die Bedingung wahr ist:
while bedingung; do befehle # werden wiederholt, solange die Bedingung wahr istdoneFür ein Menü nutzt man eine spezielle Variante: true ist ein Befehl, der immer Erfolg meldet, also läuft while true endlos. Verlassen wird die Schleife von innen mit break.
Listing 3: Die Endlosschleife
Abschnitt betitelt „Listing 3: Die Endlosschleife“# Hauptprogramm
# Endlosschleife, verlassen mit breakwhile true; do
# Menü ausgeben echo "==== Skript-Menü ==== 1 Alle Shell-Skripte auflisten 2 Neues Shell-Skript erstellen e Ende"
# Eingabe einlesen read -r -p "Auswahl eingeben, mit Enter bestätigen: " antwort echo "Du hast '$antwort' gewählt."
# Auswahl e beendet das Menü if [[ $antwort == "e" ]]; then break fi
doneDer ganze bisherige Block wandert in die Schleife und wird eingerückt (die Zeilen des Menütexts nicht, sie würden sonst eingerückt ausgegeben). Testen: Beendet e das Skript? Was passiert bei E? Falls du dich verrannt hast: Ctrl+C bricht jedes Skript ab.
Mehrfachverzweigung: case
Abschnitt betitelt „Mehrfachverzweigung: case“Für drei Menüpunkte bräuchte es drei if-Blöcke, für zehn Punkte zehn. Das wird unübersichtlich. Für die Auswahl aus mehreren Werten gibt es case:
case "$variable" in wert1) befehle # bei wert1 ;; wert2|wert3) befehle # bei wert2 oder wert3 ;; *) befehle # bei allem anderen ;;esacDie Bash vergleicht den Wert der Reihe nach mit jedem Muster und führt den ersten passenden Block aus. ;; schliesst einen Block ab. Die Muster sind dieselben wie bei Dateinamen: [eE] steht für e oder E, | trennt Alternativen, * passt auf alles und fängt am Schluss den Rest ab.
flowchart TD
M["Menü ausgeben"] --> R["Eingabe einlesen"]
R --> C1{"Eingabe = 1?"}
C1 -->|ja| A1["Aktion 1"] --> M
C1 -->|nein| C2{"Eingabe = 2?"}
C2 -->|ja| A2["Aktion 2"] --> M
C2 -->|nein| C3{"Eingabe = e oder q?"}
C3 -->|ja| E(["Ende"])
C3 -->|nein| U["Ungültige Eingabe melden"] --> M
Listing 4: case statt if
Abschnitt betitelt „Listing 4: case statt if“Die if-Verzweigung wird durch ein case ersetzt. Das Menü lässt sich neu mit e, E, q oder Q beenden:
# Eingabe einlesen read -r -p "Auswahl eingeben, mit Enter bestätigen: " antwort
# Auswahl e beendet das Menü if [[ $antwort == "e" ]]; then break fi
# Je nach Eingabe handeln case "$antwort" in 1) echo "=> Menüpunkt $antwort gewählt" # hier kommt später die Aktion hin ;; 2) echo "=> Menüpunkt $antwort gewählt" # hier kommt später die Aktion hin ;; [eE]|[qQ]) echo "=> Ende" break ;; *) echo "=> Ungültige Eingabe: '$antwort'" ;; esac
doneTesten. Verhält sich das Skript wie im Flowchart? Wenn du den Ablauf nicht erklären kannst, ist jetzt der Moment, ihn mit bash -x ./menu.sh zu beobachten: Die Bash zeigt dann jede Zeile, bevor sie sie ausführt.
Arrays und Zählschleifen
Abschnitt betitelt „Arrays und Zählschleifen“Der Menütext ist fix im echo verdrahtet. Ein neuer Menüpunkt müsste dort und im case nachgetragen werden. Besser: Die Einträge liegen in einem Array, einer Variablen mit mehreren Werten, und das Menü wird daraus erzeugt.
Listing 5: Variablen und Array
Abschnitt betitelt „Listing 5: Variablen und Array“# Variablentitel="==== Skript-Menü ===="menu=( "Alle Shell-Skripte auflisten" "Neues Shell-Skript erstellen" "Ende")anzahl=${#menu[@]}Die Werte stehen in runden Klammern, durch Leerzeichen oder Zeilenumbrüche getrennt. Die Nummerierung beginnt bei 0: ${menu[0]} ist der erste Eintrag, ${menu[2]} der dritte. ${#menu[@]} liefert die Anzahl Elemente, "${menu[@]}" alle Elemente einzeln. Die geschweiften Klammern sind bei Arrays Pflicht, ohne sie würde die Bash $menu[0] als Variable menu gefolgt vom Text [0] lesen.
Für die Ausgabe eignet sich die Zählschleife im C-Stil: for ((start; bedingung; schritt)). Die Variable i beginnt bei 0, die Schleife läuft, solange i kleiner als anzahl ist, und i++ erhöht i nach jedem Durchgang um 1. Weil das Array bei 0 beginnt, werden auch die Menüpunkte ab 0 nummeriert, und das case zieht nach.
Listing 6: Menü aus dem Array
Abschnitt betitelt „Listing 6: Menü aus dem Array“Zur Kontrolle das vollständige Skript:
#!/bin/bash## menu.sh# Interaktives Menü## 2026-11-05 / Vorname Nachname#
# Variablentitel="==== Skript-Menü ===="menu=( "Alle Shell-Skripte auflisten" "Neues Shell-Skript erstellen" "Ende")anzahl=${#menu[@]}
# Funktionen
# Hauptprogramm
# Endlosschleife, verlassen mit breakwhile true; do
# Menü ausgeben echo echo "$titel" for ((i = 0; i < anzahl; i++)); do echo " $i) ${menu[$i]}" done
# Eingabe einlesen read -r -p "Auswahl eingeben, mit Enter bestätigen: " antwort
# Je nach Eingabe handeln case "$antwort" in 0) echo "=> ${menu[0]}" # hier kommt später die Aktion hin ;; 1) echo "=> ${menu[1]}" # hier kommt später die Aktion hin ;; 2|[eE]|[qQ]) echo "=> ${menu[2]}" break ;; *) echo "=> Ungültige Eingabe: '$antwort'" ;; esac
doneTesten. Füge probehalber einen vierten Eintrag ins Array ein. Was ändert sich im Menü, was im Verhalten?
Weitere Schleifen
Abschnitt betitelt „Weitere Schleifen“for läuft auch direkt über eine Liste von Wörtern, über Dateinamen (Glob) oder über alle Argumente des Skripts:
for frucht in Apfel Banane Mango; do echo "Frucht: $frucht"done
for datei in ./*.sh; do echo "Skript: $datei"done
for argument in "$@"; do echo "Argument: $argument"doneuntil ist das Gegenstück zu while: Der Block läuft, bis die Bedingung wahr wird. Für Zähler nimmst du (( )), die arithmetische Auswertung der Bash:
i=1until (( i > 3 )); do echo "Durchgang $i" (( i += 1 ))doneAssoziative Arrays
Abschnitt betitelt „Assoziative Arrays“Neben nummerierten gibt es Arrays mit frei wählbaren Schlüsseln, die mit declare -A angelegt werden:
declare -A hauptstadt=( [Schweiz]="Bern" [Italien]="Rom" )echo "${hauptstadt[Schweiz]}"for land in "${!hauptstadt[@]}"; do echo "$land: ${hauptstadt[$land]}"done"${!hauptstadt[@]}" mit Ausrufezeichen liefert die Schlüssel, ohne Ausrufezeichen die Werte. Die Reihenfolge ist nicht festgelegt. Beide Array-Arten führt scripts/linux/08-skripte/arraydemo.sh vor.
Listing 7: Befehle hinter dem Menü
Abschnitt betitelt „Listing 7: Befehle hinter dem Menü“Was fehlt, sind die Aktionen. Punkt 0 listet die Skripte, Punkt 1 ruft addscript.sh auf:
case "$antwort" in 0) echo "=> ${menu[0]}" ls -l ./*.sh ;; 1) echo "=> ${menu[1]}" ./addscript.sh einneuesskript.sh "Test aus menu.sh" ;;Warum ./*.sh und nicht *.sh? Eine Datei namens -l.sh würde sonst als Option an ls gehen. ShellCheck meldet das als SC2035.
Testen. Wähle 0, dann 1, dann nochmals 1. Der zweite Aufruf scheitert, weil die Datei schon existiert. Das Skript ist so sinnfrei, es könnte nur ein einziges Mal ein Skript anlegen. Das zu beheben ist deine Aufgabe.
Lösung anzeigen
8.3: Menüpunkt 1 fragt beide Werte ab. Damit menu.sh sein addscript.sh auch findet, wenn es von einem anderen Verzeichnis aus gestartet wird, nimmt die Lösung den Pfad aus $0: dirname "$0" liefert das Verzeichnis, in dem menu.sh liegt.
# Variablentitel="==== Skript-Menü ===="menu=( "Alle Shell-Skripte auflisten" "Neues Shell-Skript erstellen" "Ende")anzahl=${#menu[@]}skriptpfad=$(dirname "$0") # Verzeichnis, in dem menu.sh liegt
# Hauptprogramm# ... case "$antwort" in 0) echo "=> ${menu[0]}" ls -l ./*.sh ;; 1) echo "=> ${menu[1]}" read -r -p "Name des Skripts: " skriptname read -r -p "Zweck des Skripts: " skriptzweck "$skriptpfad/addscript.sh" "$skriptname" "$skriptzweck" ;;Bei leerem Namen meldet addscript.sh den Fehler No such file or directory, weil die Argumentprüfung nur zählt, nicht prüft, ob $1 leer ist. Ein leeres Argument "" zählt. Ein [[ -z "$skriptname" ]] vor dem Aufruf fängt das ab.
8.4: Siehe scripts/linux/08-skripte/planeten.sh. Der Kern:
read -r -p "Welcher Planet interessiert dich? " planet
case "$planet" in [mM]erkur) echo "Merkur: 1. Planet, Gesteinsplanet." ;; [vV]enus) echo "Venus: 2. Planet, Gesteinsplanet." ;; [eE]rde) echo "Erde: 3. Planet, Gesteinsplanet." ;; [mM]ars) echo "Mars: 4. Planet, Gesteinsplanet." ;; [jJ]upiter) echo "Jupiter: 5. Planet, Gasriese." ;; [sS]aturn) echo "Saturn: 6. Planet, Gasriese." ;; [uU]ranus) echo "Uranus: 7. Planet, Eisriese." ;; [nN]eptun) echo "Neptun: 8. Planet, Eisriese." ;; "") echo "Keine Eingabe, kein Planet." >&2 exit 1 ;; *) echo "'$planet' kenne ich nicht." >&2 exit 1 ;;esac8.5: Ausgabe sind drei Zeilen (Apfel, Banane, Mango), dann «Kiwi, Allergie!» und Abbruch mit Status 1. Die Melone kommt nie an die Reihe.
#!/bin/bash## fruchtsalat.sh# Gibt die Früchte der Liste aus, bricht bei Kiwi ab.## 2026-11-05 / Vorname Nachname#
# Variablenfruechte=( Apfel Banane Mango Kiwi Melone )
# Hauptprogrammfor frucht in "${fruechte[@]}"; do if [[ $frucht == "Kiwi" ]]; then echo "Kiwi, Allergie!" exit 1 fi echo "Aktuelle Frucht: $frucht"doneSelbstcheck · 6 Fragen