5.4 Kontrollstrukturen

Kontrollstrukturen sind eine der mächtigsten Funktionen von Programmiersprachen. Mit ihnen lässt sich die Ausführung einer Aktion an gewisse Bedingungen binden (if/else) oder bestimmte Aktionen mehrmals wiederholen (Schleifen). In diesem Lab lernen wir, wie Kontrollstrukturen funktionieren und wie wir sie anwenden.

Inhalt

Bedingte Ausdrücke

Bevor wir in die Kontrollstrukturen eintauchen, müssen wir uns kurz mit bedingten Ausdrücken auseinandersetzen. Ein bedingter Ausdruck ist eine Anweisung, deren Ausführung entweder wahr oder falsch sein kann. Ein Beispiel:

Bash

1
2
3
number_one=10
number_two=12
[[ number_one -gt number_two ]]

Python

1
2
3
4
number_one = 10
number_two = 12
number_one > number_two
False

In den obigen Beispielen definieren wir zwei Variablen und Vergleichen anschliessend die beiden Zahlen. Dabei prüfen wir, ob die erste Zahl grösser ist, als die zweite. In beiden Fällen ist dieser Ausdruck natürlich falsch. In Python können wir zum vergleichen zweier Zahlen den grösser/kleiner Operator (>, <) verwenden. In Bash gibt es die sogenannten comparison operators. -gt steht in diesem Fall für greater than. Es gibt aber noch viele weitere comparison operators (https://tldp.org/LDP/abs/html/comparison-ops.html )

Bash

1
2
3
variable_one="text"
variable_two="text"
[[ variable_one == variable_two ]]

Python

1
2
3
4
variable_one = "text"
variable_two = "text"
variable_one == variable_two
True

If / If else / if elif else

Mit der If/else Anweisung lassen sich bestimmte Aktion ausführen, sofern eine Bedingung erfüllt ist. Die Syntax unterscheidet sich von Bash zu Python leicht:

Bash

1
2
3
if Bedingung; then
  Anweisungsblock
fi

Python

1
2
if Bedingung:
        Anweisungsblock

Die Bedingung enthält sowohl in Bash als auch in Python einen bedingten Ausdruck, während im Anweisungsblock beliebiger Code stehen kann. Wir können die bedingten Ausdrücke aus dem vorherigen Beispiel 1:1 einsetzen:

Bash

1
2
3
4
5
number_one=10
number_two=12
if [[ number_one -gt number_two ]]; then
    echo "number_one is larger than number_two"
fi

Python

1
2
3
4
5
number_one = 10
number_two = 12

if number_one > number_two:
    print("number_one is larger than number_two")

In diesem Beispiel wird natürlich nichts ausgegeben, da 10 kleiner ist als 12. Wir können nun aber eine else condition hinzufügen, welche dann ausgeführt, wenn der bedingte Ausdruck nicht zutrifft:

Bash

1
2
3
4
5
6
7
number_one=10
number_two=12
if [[ number_one -gt number_two ]]; then
  echo "number_one is larger than number_two"
else
  echo "number_one is smaller than or equal to number_two"
fi

Python

1
2
3
4
5
6
7
number_one = 10
number_two = 12

if number_one > number_two:
    print("number_one is larger than number_two")
else:
    print("number_one is smaller than or equal to number_two")

Wie du siehst, ist die else Anweisung optional und nicht in jedem Fall nötig/gewünscht. Es gibt in beiden Programmiersprachen noch eine weitere optionale Anweisung: elif. Mit elif können mehrere bedingte Ausdrücke geprüft und jeweils die dazu passende Anweisung ausgeführt werden. Auch hierzu ein kurzes Beispiel:

Bash

1
2
3
4
5
6
7
8
9
number_one=10

if [[ number_one -lt 10 ]]; then
  echo "number_one is smaller than 10"
elif [[ number_one -lt 20 ]]; then
  echo "number_one is between 10 and 20"
else
  echo "number_one is larger than 20"
fi

Python

1
2
3
4
5
6
7
8
number_one = 10

if number_one < 10:
    print("number_one is smaller than 10")
elif number_one < 20
    print("number_one between 10 and 20")
else:
    print("number_one larger than 20")

Der Interpreter der jeweiligen Programmiersprache arbeitet die einzelnen Bedingungen der Reihe nach ab. Sobald die Bedingung zutrifft, wird der entsprechende Anweisungsblock ausgeführt. Folgende Bedingungen werden übersprungen:

Schleifen

Schleifen führen die gleiche Anweisung 0, 1 oder mehrere male aus. Mit Schleifen kann man z.B. über eine Liste iterieren und für jedes Element in der Liste eine bestimmte Aktion ausführen.

while

Wie es der Name bereits vermuten lässt, wird bei der while Schleife eine Aktion solange ausgeführt, bis ein definierter Ausdruck zutrifft. Die Syntax einer while Schleife lautet:

if Bedingung: Anweisungsblock

Bash

1
2
3
while Bedingung; do
     Anweisungsblock
done

Python

1
2
while Bedingung:
    Anweisungsblock

Im folgenden Beispiel führen wir eine while Schleife so lange aus, wie die Variable counter kleiner als 3 ist. Im Anweisungsblock der Schleife erhöhen wir den counter jeweils um 1 und geben einen Text aus:

Bash

1
2
3
4
5
6
counter=0

while [[ counter -lt 3 ]]; do
    echo "in while loop"
    counter=$((counter + 1))
done

Python

1
2
3
4
5
6

counter = 0

while counter < 3:
    print("in while loop")
    counter += 1

Wenn du diese Beispiele bei dir durchspielst, wirst du feststellen, dass der Anweisungsblock insgesamt 3 mal ausgeführt wird. Sobald der Interpreter bei der Schleife ankommt, prüft er erstmals den Wert von counter. Da wir diesen mit 0 starten, trifft diese Bedingung zu (0 < 3). Nun wird der Anweisungsblock ausgeführt (Nachricht ausgeben und counter um 1 erhöhen). Am Ende des Anweisungsblockes springt der Interpreter wieder zur Bedingung und prüft jetzt erneut den Ausdruck. Da wir counter in der Schleife um 1 erhöht haben, lautet der Ausdruck neu 1 < 3 und trifft noch immer zu. Nun wird erneut der Anweisungsblock ausgeführt und das ganze wiederholt sich. In der letzten Wiederholung der Schleife wird counter von 2 auf 3 erhöht. Der Interpreter prüft nun also den folgenden Ausdruck: 3 < 3. Dieser trifft nun nicht mehr zu, der Anweisungsblock wird daher kein weiteres Mal ausgeführt.

for

For loops werden verwendet, um über eine bestimmte Sequenz zu iterieren (z.B. über alle Zahlen von 0 bis n):

Bash

1
2
3
4
for index in {0..3}; do
    # Anweisungsblock
    echo "${index}"
done

Python

1
2
3
for index in range(3):
    # Anweisungsblocl
    print(index)

In Bash können wir brace expansion verwenden, um uns eine Liste von Zahlen zu generieren. Der Ausdruck {0..3} generiert automatisch die Zeichenkette 0 1 2 3. In Python verwenden wir für den gleichen Zweck die range Funktion. Dieser Funktion können drei Parameter übergeben werden: range(start, stop, step). Die Parameter start und step sind optional und werden im obigen Beispiel nicht verwendet. Wir definieren ausschliesslich den stop Parameter mit dem Wert 3. Die Range Funktion beginnt standardmässig bei 0 und zählt dann bis zum definierten stop Parameter in Schritten (gem. dem step Parameter) hoch. Wichtig der definierte stop Wert ist in der generierten Liste nicht enthalten. range(3) generiert also die folgende Liste: 0 1 2.

In beiden Beispielen geben wir den Wert der Variable index aus. Das mag auf den ersten Blick etwas verwirrend wirken, da wir diese Variable ja nirgends explizit gesetzt habe. Die for Schleife übernimmt das für uns und speichert in der Variable index jeweils den Wert der aktuellen Iteration. Mit {0..3} definieren wir, dass wir die Schleife mit den Werten 0, 1, 2 und 3 je einmal durchführen möchten. Die Schleife wählt also bei der ersten Iteration den ersten Wert unserer Liste (in diesem Fall 0) und speichert in die Variable index. Anschliessend wird der Anweisungsblock ausgeführt. Danach wird der nächste Wert aus der Liste (1) in die Variable index gespeichert und der Anweisungsblock wird erneut ausgeführt. Das Ganze wird so lange wiederholt, bis sämtliche Elemente in der Liste abgearbeitet wurden. Wichtig: der Name der Variable ist frei wählbar. Du kannst also anstatt index auch z.B. zaehler verwenden.

for Schleifen werden sehr oft zusammen mit Arrays/Listen (siehe Lab 2) verwendet, um für jedes Element in der Liste eine bestimmte Aktion auszuführen. Im folgenden Beispiel definieren wir eine Liste von Namen und wandeln dann diese Namen mit einer for Schleife in GROSSBUCHSTABEN um:

Bash

1
2
3
4
firstnames=("Alice" "Bob")
for name in "${firstnames[@]}"; do
   echo "${name}" | tr '[:lower:]' '[:upper:]'
done

Python

1
2
3
4
firstnames = ["Alice", "Bob"]

for name in firstnames:
   print(name.upper())

Die Syntax von for Schleifen ist in den einzelnen Programmiersprachen jeweils relativ unterschiedlich. In Bash kann eine for Schleife auch so aussehen:

1
2
3
for (( c=1; c<=5; c++ )); do
   echo "${c}"
done

Funktional ist diese Schleife identisch mit:

1
2
3
for c in {0..5}; do
    echo "${c}"
done

Um diese Darstellungsform besser zu verstehen, schauen wir uns den Ausdruck (( c=1; c<=5; c++ )) etwas genauer an. Dieser ist in drei Teile unterteilt:

  • c=1: Das kennen wir bereits, wir definieren eine Variable c und weisen ihr den Wert 1 zu. Dieser Ausdruck wird ein einziges Mal vor dem ersten Durchlauf der for Schleife ausgeführt
  • c<=5: Das ist die Bedingung. Diese wird bei jedem Durchlauf geprüft. Solange die Variable c kleiner oder gleich 5 ist, wird die Schleife wiederholt.
  • c++: Dieser Ausdruck wird am Ende jedes Durchlaufs der Schleife ausgeführt. Wir erhöhen die Variable c um 1.

Iterationen steuern

Manchmal kann es nötig sein, in einer Schleife bestimmte Elemente zu überspringen oder die Schleife vorzeitig zu beenden. Hierzu gibt es zwei Schlüsselwörter, mit welchen sich das Verhalten einer Schleife steuern lässt:

  • continue: Aktuelle Iteration überspringen
  • break: Schleife beenden

Iteration überspringen oder beenden

In den folgenden Codebeispielen iterieren wir über eine Liste von Namen, überspringen jedoch die Iteration für den Namen Bob:

Bash

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/env bash

names=("Alice" "Bob" "Carol" "Carlos")

for name in "${names[@]}"; do
    if [[ $name == "Bob" ]]; then
        continue
    fi
    echo "${name}"
done

Resultat

1
2
3
4
./script.sh
Alice
Carol
Carlos

Das Gleiche lässt sich natürlich auch in Python implementieren:

1
2
3
4
5
6
7
8
#!/bin/env python

names = ["Alice", "Bob", "Carol", "Carlos"]

for name in names:
    if name == "Bob":
        continue
    print(name)

Schleife vorzeitig beenden

In den folgenden Codebeispielen iterieren wir erneut über eine Liste von Namen. Diesmal wollen wir die Schleife beenden, sobald wir den Namen Bob gefunden haben. Zusätzlich verwenden wir einen Counter, um die Position des gesuchten Namen herausfinden zu können:

Bash

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/env bash

names=("Alice" "Bob" "Carol" "Carlos")
counter=0

for name in "${names[@]}"; do
    if [[ $name == "Bob" ]]; then
        echo "found ${name} at position ${counter}"
        break
    fi
    counter=$((counter + 1))
done

Resultat:

1
2
[user@host]$ ./script.sh
found Bob at position 1

Und analog dazu das Beispiel in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/env python

names = ["Alice", "Bob", "Carol", "Carlos"]
counter = 0

for name in names:
    if name == "Bob":
        print(f"found {name} at position {counter}")
        break
    counter += 1

Das Resultat ist identisch mit demjenigen der Bash Version. Durch die Verwendung von break wird die Suche abgebrochen, sobald der Name gefunden wurde. Das Script läuft anschliessend nach der Schleife weiter.