Kontrollstrukturen sind das Fundament imperativer Programmierung. Ohne sie wäre jeder Code ein linearer Strom aus Anweisungen – starr, unbeweglich, reaktiv statt intelligent. Erst durch Verzweigungen, Schleifen und bedingte Ausführung bekommt ein Programm die Fähigkeit zur Entscheidung, zur Wiederholung, zur Dynamik.
Sie sind das, was den Unterschied macht zwischen einer bloßen Befehlsliste und einem echten, denkenden Ablauf. Wer kontrolliert, der steuert – wer steuert, der programmiert.
In Shell-Skripten müssen wir häufig Entscheidungen treffen, die den
weiteren Programmablauf bestimmen. Bedingte Anweisungen ermöglichen es,
Code nur dann auszuführen, wenn bestimmte Bedingungen erfüllt sind. Die
Bash bietet hierfür die if-Anweisung mit ihren
Erweiterungen else und elif.
Die einfachste Form einer bedingten Anweisung ist die
if-Struktur:
if BEDINGUNG
then
BEFEHLE
fiDer Befehlsblock zwischen then und fi wird
nur dann ausgeführt, wenn die BEDINGUNG “wahr” (erfolgreich) ist. In der
Shell bedeutet “wahr”, dass der Befehl einen Exit-Status von 0
zurückgibt.
Beispiel:
#!/bin/bash
# Prüfen, ob eine Datei existiert
if [ -f /etc/passwd ]
then
echo "Die Datei /etc/passwd existiert."
fiMit else können wir einen alternativen Codeblock
ausführen, wenn die Bedingung nicht erfüllt ist:
if BEDINGUNG
then
BEFEHLE_WENN_WAHR
else
BEFEHLE_WENN_FALSCH
fiBeispiel:
#!/bin/bash
# Prüfen, ob der Benutzer root ist
if [ "$(id -u)" -eq 0 ]
then
echo "Skript wird als Root ausgeführt."
else
echo "Skript wird nicht als Root ausgeführt."
fiFür komplexere Entscheidungsstrukturen mit mehreren Bedingungen
nutzen wir elif (else if):
if BEDINGUNG1
then
BEFEHLE1
elif BEDINGUNG2
then
BEFEHLE2
elif BEDINGUNG3
then
BEFEHLE3
else
STANDARD_BEFEHLE
fiBeispiel:
#!/bin/bash
# Prüfung der Tageszeit und Ausgabe einer passenden Nachricht
STUNDE=$(date +%H)
if [ $STUNDE -lt 12 ]
then
echo "Guten Morgen!"
elif [ $STUNDE -lt 18 ]
then
echo "Guten Tag!"
else
echo "Guten Abend!"
fiDie Bash bietet verschiedene Möglichkeiten,
if-Anweisungen zu schreiben:
if BEDINGUNG; then BEFEHLE; fithen-Anweisung kann auch auf der gleichen Zeile wie
if stehen:if BEDINGUNG
then
BEFEHLE
fiFür einfache bedingte Anweisungen bietet die Shell auch die
Operatoren && (AND) und || (OR):
# Führe BEFEHL2 nur aus, wenn BEFEHL1 erfolgreich war (Exit-Status 0)
BEFEHL1 && BEFEHL2
# Führe BEFEHL2 nur aus, wenn BEFEHL1 nicht erfolgreich war (Exit-Status nicht 0)
BEFEHL1 || BEFEHL2Beispiele:
# Verzeichnis erstellen und hineinwechseln (wenn Erstellung erfolgreich)
mkdir -p /tmp/test && cd /tmp/test
# Paket installieren, falls es nicht bereits installiert ist
dpkg -l | grep -q apache2 || apt-get install apache2Diese Kurzschreibweisen sind nützlich für einfache bedingte
Operationen, ersetzen aber nicht die vollständige
if-Struktur für komplexere Logik.
Bedingte Anweisungen können auch geschachtelt werden:
if BEDINGUNG1
then
if BEDINGUNG2
then
BEFEHLE
fi
fiDiese Schachtelung sollte jedoch sparsam eingesetzt werden, da sie
die Lesbarkeit des Codes beeinträchtigen kann. Häufig lassen sich
geschachtelte Bedingungen durch logische Operatoren
(&&, ||) innerhalb einer einzelnen
Bedingung ersetzen.
Um die Lesbarkeit von Shell-Skripten mit bedingten Anweisungen zu verbessern, ist eine konsistente Einrückung (Indentierung) wichtig:
if [ "$1" = "start" ]
then
echo "Starte Dienst..."
# Eingerückter Code für bessere Lesbarkeit
service_start
elif [ "$1" = "stop" ]
then
echo "Stoppe Dienst..."
service_stop
else
echo "Unbekannter Parameter: $1"
echo "Verwendung: $0 {start|stop}"
exit 1
fiBedingte Anweisungen sind ein fundamentales Werkzeug für
Shell-Skripte und ermöglichen die flexible Steuerung des Programmflusses
basierend auf verschiedenen Bedingungen. In den folgenden Abschnitten
werden wir detailliert betrachten, wie wir Bedingungen mit dem
test-Befehl formulieren können.
Im Herzen der Shell-Programmierung steht die Fähigkeit, mit dem
Dateisystem zu interagieren und Dateien sowie deren Eigenschaften zu
prüfen. Der test-Befehl, oder in seiner gebräuchlicheren
Schreibweise die eckigen Klammern [ ], bietet ein
leistungsstarkes Werkzeug, um eine Vielzahl von Dateieigenschaften zu
prüfen. Dieses Feature ist einer der größten Vorteile der Bash gegenüber
anderen Skriptsprachen bei systemnahen Operationen.
Der test-Befehl existiert in verschiedenen Formen:
# Äquivalente Schreibweisen
test AUSDRUCK
[ AUSDRUCK ]
[[ AUSDRUCK ]] # Erweiterte Bash-SyntaxDie Schreibweise mit eckigen Klammern [ ] ist am
weitesten verbreitet und am besten lesbar. Die doppelten eckigen
Klammern [[ ]] bieten erweiterte Funktionen, sind aber
Bash-spezifisch und daher nicht in allen POSIX-konformen Shells
verfügbar.
Wichtig: Bei der Verwendung von
[ ]sind Leerzeichen nach der öffnenden und vor der schließenden Klammer obligatorisch! Dies ist ein häufiger Fehler bei Einsteigern.
Hier sind die wichtigsten Dateitestoperatoren, die in Kombination mit
test oder [ ] verwendet werden:
| Operator | Prüfung | Beschreibung |
|---|---|---|
-e DATEI |
Existenz | Wahr, wenn DATEI existiert |
-f DATEI |
Reguläre Datei | Wahr, wenn DATEI existiert und eine reguläre Datei ist |
-d DATEI |
Verzeichnis | Wahr, wenn DATEI existiert und ein Verzeichnis ist |
-L DATEI |
Symbolischer Link | Wahr, wenn DATEI existiert und ein symbolischer Link ist |
-p DATEI |
Named Pipe | Wahr, wenn DATEI existiert und eine Named Pipe ist |
-S DATEI |
Socket | Wahr, wenn DATEI existiert und ein Socket ist |
-b DATEI |
Block Device | Wahr, wenn DATEI existiert und ein Block Device ist |
-c DATEI |
Character Device | Wahr, wenn DATEI existiert und ein Character Device ist |
| Operator | Prüfung | Beschreibung |
|---|---|---|
-r DATEI |
Lesbar | Wahr, wenn DATEI existiert und lesbar ist |
-w DATEI |
Schreibbar | Wahr, wenn DATEI existiert und schreibbar ist |
-x DATEI |
Ausführbar | Wahr, wenn DATEI existiert und ausführbar ist |
-u DATEI |
SUID-Bit | Wahr, wenn DATEI existiert und das Set-UID-Bit gesetzt ist |
-g DATEI |
SGID-Bit | Wahr, wenn DATEI existiert und das Set-GID-Bit gesetzt ist |
-k DATEI |
Sticky Bit | Wahr, wenn DATEI existiert und das Sticky-Bit gesetzt ist |
| Operator | Prüfung | Beschreibung |
|---|---|---|
-s DATEI |
Größe > 0 | Wahr, wenn DATEI existiert und eine Größe größer als Null hat |
-t FD |
Terminal | Wahr, wenn der Dateideskriptor FD ein Terminal ist |
-N DATEI |
Modifikation | Wahr, wenn DATEI existiert und seit dem letzten Lesen modifiziert wurde |
DATEI1 -nt DATEI2 |
Neuer als | Wahr, wenn DATEI1 neuer ist als DATEI2 (Modifikationszeit) |
DATEI1 -ot DATEI2 |
Älter als | Wahr, wenn DATEI1 älter ist als DATEI2 (Modifikationszeit) |
DATEI1 -ef DATEI2 |
Gleiche Datei | Wahr, wenn DATEI1 und DATEI2 auf die gleiche Inode verweisen |
#!/bin/bash
CONFIG_FILE="/etc/myapp/config.conf"
if [ -f "$CONFIG_FILE" ]; then
echo "Konfigurationsdatei gefunden, verarbeite..."
# Verarbeitung der Datei
else
echo "Fehler: Konfigurationsdatei nicht gefunden!"
echo "Erstelle Standardkonfiguration..."
mkdir -p "$(dirname "$CONFIG_FILE")"
echo "# Standardkonfiguration" > "$CONFIG_FILE"
fi#!/bin/bash
SCRIPT="/usr/local/bin/wichtiges_skript.sh"
if [ -x "$SCRIPT" ]; then
echo "Führe Skript aus..."
"$SCRIPT"
else
echo "Fehler: $SCRIPT ist nicht ausführbar oder existiert nicht!"
exit 1
fi#!/bin/bash
SOURCE_DIR="/home/user/dokumente"
BACKUP_DIR="/mnt/backup/dokumente"
BACKUP_MARKER="$BACKUP_DIR/.last_backup"
# Prüfung, ob das Quellverzeichnis existiert
if [ ! -d "$SOURCE_DIR" ]; then
echo "Fehler: Quellverzeichnis existiert nicht!"
exit 1
fi
# Prüfung, ob das Zielverzeichnis existiert und beschreibbar ist
if [ ! -d "$BACKUP_DIR" ] || [ ! -w "$BACKUP_DIR" ]; then
echo "Fehler: Backup-Verzeichnis existiert nicht oder ist nicht beschreibbar!"
exit 1
fi
# Prüfung, ob ein Backup notwendig ist
if [ -f "$BACKUP_MARKER" ] && [ ! "$(find "$SOURCE_DIR" -type f -newer "$BACKUP_MARKER")" ]; then
echo "Kein Backup notwendig, keine neuen oder geänderten Dateien."
exit 0
fi
# Backup durchführen
echo "Starte Backup-Prozess..."
rsync -av "$SOURCE_DIR/" "$BACKUP_DIR/"
touch "$BACKUP_MARKER"
echo "Backup abgeschlossen."# Prüfen, ob eine Datei existiert UND schreibbar ist
if [ -f "$FILE" ] && [ -w "$FILE" ]; then
echo "Datei existiert und ist schreibbar"
fi
# Prüfen, ob eine Datei ein Verzeichnis ODER ein symbolischer Link ist
if [ -d "$PATH" ] || [ -L "$PATH" ]; then
echo "Pfad ist ein Verzeichnis oder ein symbolischer Link"
fiDie doppelten eckigen Klammern [[ ]] bieten zusätzliche
Funktionen:
# Pattern Matching mit Wildcards
if [[ "$FILENAME" == *.jpg ]]; then
echo "Datei ist ein JPEG-Bild"
fi
# Reguläre Ausdrücke
if [[ "$EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Gültige E-Mail-Adresse"
fi# Über alle ausführbaren Dateien in einem Verzeichnis iterieren
for file in /usr/local/bin/*; do
if [ -x "$file" ] && [ ! -d "$file" ]; then
echo "Ausführbare Datei gefunden: $file"
fi
doneVariablen immer in Anführungszeichen setzen, um Probleme mit Leerzeichen oder Sonderzeichen zu vermeiden:
if [ -f "$FILENAME" ]; then # Richtig
if [ -f $FILENAME ]; then # Falsch (wenn FILENAME Leerzeichen enthält)Pfade mit relativen und absoluten Pfaden testen:
# Absoluter Pfad für systemweite Dateien
if [ -f "/etc/hosts" ]; then
# ...
fi
# Relativer Pfad für Dateien im aktuellen Kontext
if [ -f "./config.ini" ]; then
# ...
fiNegation für Fehlerbehandlung verwenden:
if [ ! -d "$DIR" ]; then
echo "Fehler: Verzeichnis existiert nicht!"
exit 1
fiVorsicht bei Symbolischen Links: -L
prüft, ob etwas ein symbolischer Link ist, während andere Tests dem Link
folgen:
# Prüft, ob es sich um einen symbolischen Link handelt
if [ -L "$PATH" ]; then
echo "Ist ein symbolischer Link"
fi
# Prüft, ob das Ziel des Links ein Verzeichnis ist
if [ -d "$PATH" ]; then
echo "Ist ein Verzeichnis (oder Link auf ein Verzeichnis)"
fiFehlende Anführungszeichen bei Variablen:
FILE="Mein Dokument.txt"
if [ -f $FILE ]; then # Fehlerhaft! Bash interpretiert als: [ -f Mein Dokument.txt ]
# ...
fi
# Korrekt:
if [ -f "$FILE" ]; then
# ...
fiFehlende Leerzeichen innerhalb der eckigen Klammern:
if [-f "$FILE" ]; then # Fehler: Bash interpretiert [-f als Befehl
# ...
fi
# Korrekt:
if [ -f "$FILE" ]; then
# ...
fiVergessen der Existenzprüfung vor anderen Tests:
# Gefährlich: Könnte zu irreführenden Fehlermeldungen führen, wenn die Datei nicht existiert
if [ -w "$FILE" ]; then
# ...
fi
# Sicherer:
if [ -e "$FILE" ] && [ -w "$FILE" ]; then
# ...
fiVerwechslung von -e (existiert) und
-f (ist reguläre Datei):
# Prüft nur, ob etwas existiert (könnte auch ein Verzeichnis sein)
if [ -e "$PATH" ]; then
# ...
fi
# Prüft, ob es eine reguläre Datei ist
if [ -f "$PATH" ]; then
# ...
fi#!/bin/bash
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//g')
THRESHOLD=90
if [ "$DISK_USAGE" -gt "$THRESHOLD" ]; then
echo "WARNUNG: Festplattennutzung bei ${DISK_USAGE}% überschreitet den Schwellenwert von ${THRESHOLD}%!"
# Prüfen, ob das Log-Verzeichnis existiert und beschreibbar ist
LOG_DIR="/var/log/disk-alerts"
if [ ! -d "$LOG_DIR" ]; then
if mkdir -p "$LOG_DIR" 2>/dev/null; then
echo "Log-Verzeichnis erstellt: $LOG_DIR"
else
echo "Fehler: Konnte Log-Verzeichnis nicht erstellen!" >&2
LOG_DIR="/tmp" # Fallback auf /tmp
fi
fi
# Log-Datei schreiben
LOG_FILE="$LOG_DIR/disk-alert-$(date +%Y%m%d).log"
echo "$(date): Festplattennutzung bei ${DISK_USAGE}%" >> "$LOG_FILE"
# Weitere Aktionen...
else
echo "Festplattennutzung normal (${DISK_USAGE}%)"
fi#!/bin/bash
BACKUP_FILE="/mnt/backup/system-backup-$(date +%Y%m%d).tar.gz"
MIN_SIZE=$((1024 * 1024 * 100)) # 100 MB Mindestgröße
# Prüfen, ob die Backup-Datei existiert
if [ ! -f "$BACKUP_FILE" ]; then
echo "FEHLER: Backup-Datei nicht gefunden: $BACKUP_FILE" >&2
exit 1
fi
# Größenprüfung
FILE_SIZE=$(stat -c %s "$BACKUP_FILE")
if [ "$FILE_SIZE" -lt "$MIN_SIZE" ]; then
echo "WARNUNG: Backup-Datei ist verdächtig klein: $(numfmt --to=iec-i --suffix=B $FILE_SIZE)" >&2
exit 2
fi
# Integritätsprüfung
if command -v gzip -t "$BACKUP_FILE" >/dev/null 2>&1; then
echo "Backup-Datei ist valide."
else
echo "FEHLER: Backup-Datei ist beschädigt!" >&2
exit 3
fi
# Berechtigungsprüfung
if [ ! -r "$BACKUP_FILE" ]; then
echo "WARNUNG: Backup-Datei ist nicht lesbar!" >&2
chmod +r "$BACKUP_FILE" || exit 4
fi
echo "Backup-Validierung erfolgreich abgeschlossen."Die Beherrschung der Dateisystemtests und Berechtigungsprüfungen in Bash-Skripten ist einer der wichtigsten Aspekte erfolgreicher Systemadministration und Automatisierung unter Linux/Unix. Mit diesem Arsenal an Testoperatoren können Sie robuste, sichere und zuverlässige Skripte schreiben, die elegant mit dem Dateisystem interagieren.
Eine wesentliche Komponente bedingter Anweisungen in Shell-Skripten ist die Fähigkeit, Werte zu vergleichen. Die Bash bietet spezifische Operatoren für den Vergleich von Zahlen und Strings, die sich in ihrer Syntax und Anwendung unterscheiden. Das Verständnis dieser Operatoren ist entscheidend für die Erstellung zuverlässiger und präziser Bedingungen.
Für den Vergleich von Zahlen verwendet die Bash spezielle Operatoren,
die innerhalb der test-Umgebung (also zwischen
[ ]) stehen:
| Operator | Bedeutung | Beispiel |
|---|---|---|
-eq |
Equal (gleich) | [ "$a" -eq "$b" ] |
-ne |
Not Equal (ungleich) | [ "$a" -ne "$b" ] |
-gt |
Greater Than (größer als) | [ "$a" -gt "$b" ] |
-ge |
Greater or Equal (größer oder gleich) | [ "$a" -ge "$b" ] |
-lt |
Less Than (kleiner als) | [ "$a" -lt "$b" ] |
-le |
Less or Equal (kleiner oder gleich) | [ "$a" -le "$b" ] |
#!/bin/bash
# Überprüfen des verfügbaren Festplattenspeichers
AVAILABLE_SPACE=$(df -k / | awk 'NR==2 {print $4}')
MIN_REQUIRED=1048576 # 1 GB in Kilobytes
if [ "$AVAILABLE_SPACE" -lt "$MIN_REQUIRED" ]; then
echo "Warnung: Weniger als 1 GB Festplattenspeicher verfügbar!"
echo "Verfügbar: $((AVAILABLE_SPACE / 1024)) MB"
else
echo "Ausreichend Festplattenspeicher vorhanden."
echo "Verfügbar: $((AVAILABLE_SPACE / 1024)) MB"
fiFür den Vergleich von Zeichenketten stehen folgende Operatoren zur Verfügung:
| Operator | Bedeutung | Beispiel |
|---|---|---|
= oder == |
Gleich (Inhaltsvergleich) | [ "$a" = "$b" ] |
!= |
Ungleich | [ "$a" != "$b" ] |
< |
Lexikografisch kleiner | [ "$a" \< "$b" ] |
> |
Lexikografisch größer | [ "$a" \> "$b" ] |
-z |
Leerer String (Zero length) | [ -z "$a" ] |
-n |
Nicht-leerer String (Non-zero length) | [ -n "$a" ] |
Wichtig: Bei den lexikografischen Vergleichen (
<und>) müssen die Operatoren in[ ]mit einem Backslash maskiert werden, um eine Interpretation als I/O-Umleitungen zu verhindern.
#!/bin/bash
USERNAME="admin"
PASSWORD="geheim123"
echo -n "Benutzername: "
read INPUT_USER
echo -n "Passwort: "
read -s INPUT_PASS
echo # Zeilenumbruch nach Passwort-Eingabe
if [ -z "$INPUT_USER" ]; then
echo "Fehler: Benutzername darf nicht leer sein!"
exit 1
fi
if [ "$INPUT_USER" = "$USERNAME" ] && [ "$INPUT_PASS" = "$PASSWORD" ]; then
echo "Login erfolgreich. Willkommen, $USERNAME!"
else
echo "Fehler: Falsche Anmeldedaten!"
exit 1
fi[ ] und [[ ]]Die Bash bietet mit [[ ]] eine erweiterte Syntax für
Vergleiche, die einige Vorteile bietet:
Keine Notwendigkeit für Maskierung:
# Mit [ ] - Maskierung nötig
[ "$a" \< "$b" ]
# Mit [[ ]] - Keine Maskierung nötig
[[ "$a" < "$b" ]]Unterstützung für Pattern Matching:
# Prüfen, ob eine Zeichenkette einem Muster entspricht
[[ "$filename" == *.txt ]]Unterstützung für reguläre Ausdrücke:
# Prüfen, ob eine E-Mail-Adresse gültig ist
[[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]Intelligenterer Umgang mit leeren Variablen:
# Bei [ ] könnte dies zu einem Fehler führen, wenn $var leer ist
[ $var = "test" ]
# Bei [[ ]] ist dies sicher
[[ $var = "test" ]]Hinweis: Die
[[ ]]-Syntax ist eine Bash-Erweiterung und nicht POSIX-konform. Wenn Sie Portabilität zu anderen Shells benötigen, verwenden Sie die traditionelle[ ]-Syntax.
(( ))Für rein arithmetische Vergleiche bietet die Bash die
(( ))-Syntax, die mathematische Notationen und C-ähnliche
Operatoren unterstützt:
# Arithmetische Vergleiche
if (( a > b )); then
echo "a ist größer als b"
fi
# Arithmetische Ausdrücke
if (( (a + b) * c > 100 )); then
echo "Das Ergebnis ist größer als 100"
fi| Operator | Bedeutung |
|---|---|
== |
Gleich |
!= |
Ungleich |
> |
Größer als |
>= |
Größer oder gleich |
< |
Kleiner als |
<= |
Kleiner oder gleich |
#!/bin/bash
# Mindestversion für eine Anwendung prüfen
REQUIRED_VERSION="2.5"
CURRENT_VERSION=$(myapp --version | awk '{print $2}')
# Versionsteile extrahieren
REQUIRED_MAJOR=$(echo "$REQUIRED_VERSION" | cut -d. -f1)
REQUIRED_MINOR=$(echo "$REQUIRED_VERSION" | cut -d. -f2)
CURRENT_MAJOR=$(echo "$CURRENT_VERSION" | cut -d. -f1)
CURRENT_MINOR=$(echo "$CURRENT_VERSION" | cut -d. -f2)
if [ "$CURRENT_MAJOR" -lt "$REQUIRED_MAJOR" ] ||
([ "$CURRENT_MAJOR" -eq "$REQUIRED_MAJOR" ] && [ "$CURRENT_MINOR" -lt "$REQUIRED_MINOR" ]); then
echo "Fehler: myapp Version $REQUIRED_VERSION oder höher wird benötigt!"
echo "Aktuelle Version: $CURRENT_VERSION"
exit 1
fi
echo "Versionsanforderung erfüllt. Fahre fort..."#!/bin/bash
echo "Möchten Sie fortfahren? (j/n): "
read ANSWER
# String-Vergleich mit Berücksichtigung von Groß- und Kleinschreibung
if [[ "${ANSWER,,}" == "j" || "${ANSWER,,}" == "ja" ]]; then
echo "Fortfahren..."
elif [[ "${ANSWER,,}" == "n" || "${ANSWER,,}" == "nein" ]]; then
echo "Abbruch."
exit 0
else
echo "Ungültige Eingabe. Bitte 'j' oder 'n' eingeben."
exit 1
fi#!/bin/bash
# Prüfen, ob ein Text einer bestimmten Struktur entspricht
LOG_LINE="2023-04-10 14:25:32 ERROR: Connection timeout"
# Mit regulären Ausdrücken prüfen
if [[ "$LOG_LINE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}\ (ERROR|WARNING|INFO): ]]; then
echo "Gültiges Log-Format"
# Schweregrad extrahieren
if [[ "$LOG_LINE" =~ ERROR ]]; then
echo "Schweregrad: ERROR - Kritischer Fehler!"
elif [[ "$LOG_LINE" =~ WARNING ]]; then
echo "Schweregrad: WARNING - Beachten Sie diese Meldung"
else
echo "Schweregrad: INFO - Nur zur Information"
fi
else
echo "Ungültiges Log-Format"
fiVariablen in Anführungszeichen setzen um Probleme mit Leerzeichen und Sonderzeichen zu vermeiden:
# Gut
if [ "$variable" = "wert" ]; then
# ...
fi
# Problematisch, wenn $variable Leerzeichen enthält
if [ $variable = "wert" ]; then
# ...
fiFür komplexe String-Manipulationen [[ ]]
verwenden:
# Prüfen, ob ein String mit einem bestimmten Präfix beginnt
if [[ "$filename" == prefix_* ]]; then
# ...
fiFür Zahlenvergleiche die passenden Operatoren verwenden:
# Falsch (String-Vergleich)
if [ "$count" = "5" ]; then
# ...
fi
# Richtig (numerischer Vergleich)
if [ "$count" -eq 5 ]; then
# ...
fiFür komplexe arithmetische Ausdrücke (( ))
verwenden:
if (( count * factor > threshold )); then
# ...
fiBei der Verwendung von test oder
[ ] auch auf leere Variablen achten:
# Sicherer Vergleich, auch wenn $var leer ist
if [ -n "$var" ] && [ "$var" = "wert" ]; then
# ...
fiDie richtige Verwendung von Vergleichsoperatoren ist ein wesentlicher Bestandteil jedes Shell-Skripts. Mit dem Verständnis der unterschiedlichen Operatoren für Zahlen- und String-Vergleiche sowie deren korrekte Anwendung können Sie robuste und zuverlässige Bedingungen formulieren, die die Grundlage für fortgeschrittene Shell-Skripte bilden.
Logische Operatoren sind unverzichtbare Werkzeuge in der Shell-Programmierung, da sie es ermöglichen, mehrere Bedingungen zu kombinieren oder zu negieren. Mit ihrer Hilfe können komplexe Entscheidungsstrukturen aufgebaut werden, die auf verschiedenen Bedingungen basieren. Die Bash bietet verschiedene Möglichkeiten, diese Operationen durchzuführen, jeweils mit eigenen Syntax- und Anwendungsbereichen.
In der Bash gibt es drei grundlegende logische Operatoren:
| Operator | Bedeutung | Verwendung in [ ] |
Verwendung in [[ ]] |
Verwendung als Befehlsverbinder |
|---|---|---|---|---|
| AND | Beide Bedingungen müssen wahr sein | -a |
&& |
&& |
| OR | Mindestens eine Bedingung muss wahr sein | -o |
|| |
|| |
| NOT | Negiert eine Bedingung | ! |
! |
! |
[ ]-Tests mit -a und -o# AND-Verknüpfung mit -a
if [ "$age" -ge 18 -a "$has_id" = "true" ]; then
echo "Zutritt gewährt."
fi
# OR-Verknüpfung mit -o
if [ "$is_admin" = "true" -o "$is_superuser" = "true" ]; then
echo "Administrator-Rechte gewährt."
fi
# Negation mit !
if [ ! -f "$config_file" ]; then
echo "Konfigurationsdatei existiert nicht!"
fiHinweis: Die Verwendung von
-aund-oinnerhalb eines einzelnen[ ]-Tests ist zwar möglich, wird aber wegen potenzieller Mehrdeutigkeiten bei komplexeren Ausdrücken nicht empfohlen. Besser ist die Verwendung separater Tests mit&&und||.
&& und ||# AND-Verknüpfung mit &&
if [ "$age" -ge 18 ] && [ "$has_id" = "true" ]; then
echo "Zutritt gewährt."
fi
# OR-Verknüpfung mit ||
if [ "$is_admin" = "true" ] || [ "$is_superuser" = "true" ]; then
echo "Administrator-Rechte gewährt."
fi
# Kombination von AND und OR mit expliziter Gruppierung
if [ "$is_weekend" = "true" ] && ([ "$is_admin" = "true" ] || [ "$has_override" = "true" ]); then
echo "Wochenend-Zugriff gewährt."
fi[[ ]]-SyntaxDie [[ ]]-Syntax bietet eine natürlichere Verwendung
logischer Operatoren:
# AND-Verknüpfung
if [[ "$age" -ge 18 && "$has_id" = "true" ]]; then
echo "Zutritt gewährt."
fi
# OR-Verknüpfung
if [[ "$is_admin" = "true" || "$is_superuser" = "true" ]]; then
echo "Administrator-Rechte gewährt."
fi
# Negation
if [[ ! -f "$config_file" ]]; then
echo "Konfigurationsdatei existiert nicht!"
fi
# Komplexere Ausdrücke
if [[ ("$is_weekend" = "true" && "$is_holiday" = "false") || "$emergency" = "true" ]]; then
echo "Spezialzugang gewährt."
fiLogische Operatoren können auch für die Verkettung von Befehlen verwendet werden:
# Führe command2 nur aus, wenn command1 erfolgreich war (AND)
command1 && command2
# Führe command2 nur aus, wenn command1 nicht erfolgreich war (OR)
command1 || command2
# Führe command2 nur aus, wenn command1 nicht erfolgreich war, dann command3
command1 || command2 && command3#!/bin/bash
# Prüfen, ob ein Paket installiert ist, andernfalls installieren
dpkg -l | grep -q "apache2" || {
echo "Apache2 nicht gefunden, installiere..."
apt-get update && apt-get install -y apache2 || {
echo "Fehler bei der Installation von Apache2!" >&2
exit 1
}
}
# Konfiguration anpassen, wenn die Datei existiert
if [ -f "/etc/apache2/apache2.conf" ]; then
echo "Konfiguriere Apache2..."
cp /etc/apache2/apache2.conf /etc/apache2/apache2.conf.bak && \
sed -i 's/Timeout 300/Timeout 600/' /etc/apache2/apache2.conf && \
echo "Apache2 erfolgreich konfiguriert."
fi
# Service neustarten, wenn alles erfolgreich war
systemctl restart apache2 && echo "Apache2 neu gestartet." || echo "Fehler beim Neustart von Apache2!"Ein wichtiges Konzept bei logischen Operatoren ist die Kurzschlussauswertung:
&& (AND): Wenn der erste Ausdruck falsch
ist, wird der zweite nicht mehr ausgewertet.|| (OR): Wenn der erste Ausdruck wahr ist, wird der
zweite nicht mehr ausgewertet.Dies kann gezielt zur Steuerung des Programmflusses genutzt werden:
# Verzeichnis erstellen und hineinwechseln (nur wenn Erstellung erfolgreich)
mkdir -p /tmp/workdir && cd /tmp/workdir
# Fallback bei Fehler
ping -c 1 server.example.com || echo "Server nicht erreichbar!"
# Kombination für Fehlerbehandlung
cp wichtige_datei.txt /backup/ || {
echo "Backup fehlgeschlagen!" >&2
send_alert_email
exit 1
}#!/bin/bash
# Prüfungen vor einem Systemupdate
DISK_SPACE=$(df -k / | awk 'NR==2 {print $4}')
RAM_FREE=$(free -m | awk 'NR==2 {print $4}')
NETWORK=$(ping -c 1 updates.example.com >/dev/null && echo "online" || echo "offline")
BATTERY=$(acpi -b 2>/dev/null | grep -q "Discharging" && echo "discharging" || echo "charging")
# Komplexe Bedingungsprüfung
if [[ "$DISK_SPACE" -gt 1048576 && "$RAM_FREE" -gt 512 && "$NETWORK" = "online" ]] && \
[[ "$BATTERY" = "charging" || $(id -u) -eq 0 ]]; then
echo "Alle Bedingungen für ein Update sind erfüllt."
apt-get update && apt-get upgrade -y
else
echo "Update abgebrochen. Nicht alle Bedingungen erfüllt:"
[ "$DISK_SPACE" -le 1048576 ] && echo "- Zu wenig Festplattenplatz"
[ "$RAM_FREE" -le 512 ] && echo "- Zu wenig freier Arbeitsspeicher"
[ "$NETWORK" != "online" ] && echo "- Keine Netzwerkverbindung"
[ "$BATTERY" = "discharging" ] && [ "$(id -u)" -ne 0 ] && \
echo "- Batterie wird entladen und keine Root-Rechte"
fi#!/bin/bash
# Verarbeite Dateien, aber brich ab, wenn ein Fehler auftritt oder nach 10 Dateien
COUNT=0
MAX_FILES=10
ERROR=false
for file in *.txt; do
# Abbruch, wenn MAX_FILES erreicht oder ERROR gesetzt
[[ "$COUNT" -ge "$MAX_FILES" || "$ERROR" = "true" ]] && break
echo "Verarbeite $file..."
# Fehler-Flag setzen bei Problemen
process_file "$file" || ERROR=true
# Zähler erhöhen
((COUNT++))
done
# Ausgabe nach Schleifenende
if [ "$ERROR" = "true" ]; then
echo "Verarbeitung wegen eines Fehlers abgebrochen."
elif [ "$COUNT" -ge "$MAX_FILES" ]; then
echo "Maximale Anzahl an Dateien ($MAX_FILES) verarbeitet."
else
echo "Alle Dateien erfolgreich verarbeitet."
fiKlare Strukturierung bei komplexen Bedingungen:
# Besser lesbar mit Einrückung und Zeilenumbrüchen
if [[ "$condition1" = true &&
"$condition2" = true ]] ||
[[ "$override" = true ]]; then
# ...
fiVerwendung von Klammern für explizite Gruppierung:
# Ohne Klammern: möglicherweise unerwartete Auswertungsreihenfolge
if [ "$a" = true ] && [ "$b" = true ] || [ "$c" = true ]; then
# ...
fi
# Mit Klammern: klare Gruppierung
if ([ "$a" = true ] && [ "$b" = true ]) || [ "$c" = true ]; then
# ...
fiBevorzugung von [[ ]] für komplexe
Bedingungen:
# Besser: Verwenden von [[ ]] für komplexe Bedingungen
if [[ "$string" == *pattern* && "$number" -gt 10 ]]; then
# ...
fiVorsicht bei der Mischung verschiedener logischer Stile:
# Vermeiden: Mischung von -a/-o und &&/||
if [ "$a" = true -a "$b" = true ] && [ "$c" = true ]; then
# ...
fi
# Besser: Konsistente Verwendung eines Stils
if [[ "$a" = true && "$b" = true && "$c" = true ]]; then
# ...
fiExplizite Tests auf boolesche Werte:
# Kürzer, aber weniger deutlich
if [ "$success" ]; then
# ...
fi
# Expliziter und klarer
if [ "$success" = true ]; then
# ...
fiFehlerhafte Priorität bei logischen Operatoren:
# Möglicherweise unerwartetes Verhalten
if [ "$a" = true ] || [ "$b" = true ] && [ "$c" = true ]; then
# Dies wird ausgeführt, wenn $a wahr ist, ODER wenn beide $b und $c wahr sind
fiSyntaxfehler bei Verwendung von -a und
-o in separaten Tests:
# Fehler: -a nur innerhalb eines einzelnen [ ]-Tests gültig
if [ "$a" = true ] -a [ "$b" = true ]; then
# ...
fi
# Korrekt
if [ "$a" = true -a "$b" = true ]; then
# ...
fi
# Oder besser
if [ "$a" = true ] && [ "$b" = true ]; then
# ...
fiUnbeabsichtigte Ausführung bei Befehlsverkettung:
# Vorsicht: Bei Fehler in command1 wird command2 ausgeführt
command1 || command2
# Bei einer Gruppe von Befehlen als Fehlerbehandlung Klammern verwenden
command1 || {
echo "Fehler in command1"
command2
exit 1
}Die richtige Anwendung logischer Operatoren ist entscheidend für die Entwicklung zuverlässiger Shell-Skripte. Durch das Verständnis der verschiedenen Syntaxformen und deren gezielten Einsatz können selbst komplexe Entscheidungsstrukturen klar und wartbar umgesetzt werden.
Schleifen sind unerlässliche Bausteine in der Shell-Programmierung,
die es ermöglichen, Befehle oder Codeblöcke wiederholt auszuführen. Die
Bash bietet drei Haupttypen von Schleifen: for,
while und until. Jede dieser
Schleifenstrukturen hat eigene Charakteristika und eignet sich für
unterschiedliche Anwendungsfälle.
Die for-Schleife ist vermutlich die am häufigsten
verwendete Schleifenstruktur in Shell-Skripten. Sie iteriert über eine
Liste von Werten und führt für jeden Wert einen Codeblock aus.
for VARIABLE in WERTE; do
BEFEHLE
doneIteration über eine explizite Liste:
#!/bin/bash
# Über eine explizite Liste von Werten iterieren
for name in Alice Bob Charlie Dave; do
echo "Hallo, $name!"
doneIteration über Dateien mit Globbing (Wildcards):
#!/bin/bash
# Über alle .txt-Dateien im aktuellen Verzeichnis iterieren
for file in *.txt; do
# Prüfen, ob Dateien existieren (vermeidet Probleme, wenn keine .txt-Dateien vorhanden sind)
if [ -f "$file" ]; then
echo "Verarbeite Datei: $file"
# Weitere Verarbeitung hier...
fi
doneIteration über Befehlsausgabe:
#!/bin/bash
# Über die Ausgabe eines Befehls iterieren (Liste aktiver Benutzer)
for user in $(who | cut -d' ' -f1 | sort -u); do
echo "Benutzer $user ist angemeldet."
doneTraditionelle numerische Schleife:
#!/bin/bash
# Zählen von 1 bis 10
for i in {1..10}; do
echo "Zahl: $i"
done
# Erweiterte Syntax mit Schrittweite (Bash 4.0+)
for i in {0..20..5}; do # 0, 5, 10, 15, 20
echo "Zahl mit Schritt: $i"
doneC-ähnliche for-Schleife:
#!/bin/bash
# C-ähnliche Syntax für mehr Flexibilität
for ((i=0; i<5; i++)); do
echo "Index: $i"
doneDie while-Schleife führt einen Codeblock aus, solange
eine bestimmte Bedingung wahr (true) ist.
while BEDINGUNG; do
BEFEHLE
doneEinfache Zählschleife:
#!/bin/bash
# Zählen von 1 bis 5
count=1
while [ $count -le 5 ]; do
echo "Zähler: $count"
((count++))
doneDateieingabe zeilenweise lesen:
#!/bin/bash
# Eine Datei zeilenweise lesen
while IFS= read -r line; do
echo "Gelesen: $line"
done < "input.txt"Warten, bis eine Bedingung erfüllt ist:
#!/bin/bash
# Warten, bis ein Prozess beendet ist
while pgrep -x "apt-get" > /dev/null; do
echo "Warte auf Abschluss von apt-get..."
sleep 2
done
echo "Prozess beendet, fahre fort..."Endlosschleife mit expliziter Abbruchbedingung:
#!/bin/bash
# Endlosschleife (mit break zum Abbruch)
while true; do
echo "Geben Sie einen Befehl ein (oder 'exit' zum Beenden):"
read cmd
if [ "$cmd" = "exit" ]; then
break
fi
# Ausführen des eingegebenen Befehls
eval "$cmd"
doneDie until-Schleife ist das logische Gegenstück zur
while-Schleife. Sie führt einen Codeblock aus, bis eine
bestimmte Bedingung wahr wird (also solange die Bedingung falsch
ist).
until BEDINGUNG; do
BEFEHLE
doneWarten auf Verfügbarkeit eines Dienstes:
#!/bin/bash
# Warten, bis ein Webserver verfügbar ist
until curl -s --head http://localhost:8080 | grep "200 OK" > /dev/null; do
echo "Warte auf Webserver..."
sleep 5
done
echo "Webserver ist jetzt verfügbar!"Countdown:
#!/bin/bash
# Countdown von 10 auf 0
counter=10
until [ $counter -lt 0 ]; do
echo "Countdown: $counter"
((counter--))
sleep 1
done
echo "Start!"Wiederholungsversuche mit Begrenzung:
#!/bin/bash
# Versuchen, eine Verbindung herzustellen, mit begrenzter Anzahl an Versuchen
attempts=0
max_attempts=5
until ping -c 1 -W 1 example.com > /dev/null || [ $attempts -ge $max_attempts ]; do
attempts=$((attempts + 1))
echo "Verbindungsversuch $attempts von $max_attempts fehlgeschlagen, versuche erneut..."
sleep 2
done
if [ $attempts -lt $max_attempts ]; then
echo "Verbindung erfolgreich hergestellt."
else
echo "Maximale Anzahl an Versuchen erreicht. Konnte keine Verbindung herstellen."
exit 1
fi| Schleifentyp | Verwendungszweck | Besonderheiten |
|---|---|---|
for |
Iteration über bekannte Listen von Elementen | Sehr vielseitig, einfach zu verwenden für Listen, Bereiche, Dateien |
while |
Ausführung, solange eine Bedingung wahr ist | Gut für unbekannte Anzahl von Iterationen, Dateioperationen, interaktive Schleifen |
until |
Ausführung, bis eine Bedingung wahr wird | Praktisch für Warten auf Ereignisse, Timeouts, semantisch manchmal
klarer als while mit negierter Bedingung |
Schleifen können auch ineinander verschachtelt werden, was für komplexere Algorithmen nützlich ist:
#!/bin/bash
# Generieren einer Multiplikationstabelle
for i in {1..5}; do
for j in {1..5}; do
product=$((i * j))
printf "%3d " "$product"
done
echo # Zeilenumbruch nach jeder inneren Schleife
done#!/bin/bash
# Dynamische Bereiche basierend auf Variablen oder Befehlen
start=5
end=$(wc -l < input.txt)
for ((i=start; i<=end; i++)); do
echo "Verarbeite Zeile $i"
# Verarbeitung hier...
done#!/bin/bash
# Parallele Ausführung von Aufgaben mit maximaler Anzahl gleichzeitiger Prozesse
MAX_PROCS=4
count=0
for job in job1 job2 job3 job4 job5 job6 job7 job8; do
# Starte Hintergrundprozess
(
echo "Starte Job: $job"
sleep $((RANDOM % 5 + 1)) # Simuliere Arbeit
echo "Job $job abgeschlossen"
) &
# Zähle laufende Prozesse
((count++))
# Wenn maximale Anzahl erreicht ist, warte auf Beendigung eines Prozesses
if [ $count -ge $MAX_PROCS ]; then
wait -n # Warte auf Beendigung eines Hintergrundprozesses
((count--))
fi
done
# Warte auf alle verbleibenden Hintergrundprozesse
wait
echo "Alle Jobs abgeschlossen"Variablen in Anführungszeichen setzen, insbesondere bei Dateinamen:
for file in *.txt; do
echo "Verarbeite \"$file\"" # Anführungszeichen verhindern Probleme mit Leerzeichen
doneIFS (Internal Field Separator) für zeilenweise Verarbeitung anpassen:
# Sichere Methode zum Lesen von Zeilen mit Leerzeichen
while IFS= read -r line; do
echo "Zeile: $line"
done < input.txtVermeiden von Subshells bei Dateieingabe, wenn möglich:
# Besser: Direkte Eingabeumleitung
while read -r line; do
process_line "$line"
done < input.txt
# Vermeiden (erzeugt Subshell, Variablen gehen außerhalb verloren):
cat input.txt | while read -r line; do
process_line "$line"
donePrüfen, ob Globbing-Muster Ergebnisse liefert:
# Verhindert Fehler, wenn keine .log-Dateien existieren
shopt -s nullglob # Leere Liste statt des Musters, wenn keine Übereinstimmung
for logfile in *.log; do
if [ -f "$logfile" ]; then # Zusätzliche Prüfung für Sicherheit
process_log "$logfile"
fi
doneFortschrittsanzeige bei langen Schleifen:
total=$(find . -type f | wc -l)
current=0
find . -type f | while read -r file; do
((current++))
printf "\rVerarbeite Datei %d von %d (%d%%)" "$current" "$total" "$((current * 100 / total))"
process_file "$file"
done
echo # Abschließender ZeilenumbruchSubshell-Problem bei Pipelines:
# Problem: count bleibt 0, da die Schleife in einer Subshell läuft
count=0
cat data.txt | while read -r line; do
((count++))
done
echo "Zeilen: $count" # Wird immer 0 ausgeben!
# Lösung: Prozesssubstitution oder direkte Eingabeumleitung
count=0
while read -r line; do
((count++))
done < data.txt
echo "Zeilen: $count" # Korrektes ErgebnisUnerwartetes Globbing-Verhalten:
# Problem: Wenn keine .txt-Dateien existieren, wird "*.txt" wörtlich verwendet
for file in *.txt; do
echo "Datei: $file" # Könnte "Datei: *.txt" ausgeben!
done
# Lösung:
shopt -s nullglob # Leere Liste bei keiner Übereinstimmung
for file in *.txt; do
echo "Datei: $file"
doneEndlosschleifen vermeiden:
# Potenzielles Problem: Endlosschleife, wenn Bedingung nie falsch wird
while [ true ]; do
# Sicherstellen, dass mindestens eine Abbruchbedingung existiert
if [ "$condition" = true ]; then
break
fi
# Oder einen Timeout einbauen
((iterations++))
if [ $iterations -gt $MAX_ITERATIONS ]; then
echo "Maximale Anzahl an Iterationen erreicht, breche ab."
break
fi
doneBehandlung leerer Listen:
# Problem: Schleife wird einmal ausgeführt, auch wenn die Liste leer ist
result=$(some_command) # Könnte leer sein
for item in $result; do
echo "Item: $item" # Wird "Item: " ausgeben, wenn $result leer ist!
done
# Lösung:
result=$(some_command)
if [ -n "$result" ]; then
for item in $result; do
echo "Item: $item"
done
else
echo "Keine Elemente zum Verarbeiten."
fiSchleifen sind ein mächtiges Werkzeug in der Shell-Programmierung und ermöglichen die effiziente Automatisierung repetitiver Aufgaben. Mit dem Verständnis der verschiedenen Schleifentypen und ihrer Anwendungsbereiche können Sie elegante und effiziente Skripte erstellen, die selbst komplexe Automatisierungsaufgaben bewältigen.
Während Schleifen einen Codeblock wiederholen, ist es oft notwendig,
die normale Schleifenausführung zu unterbrechen oder bestimmte
Iterationen zu überspringen. Die Bash bietet hierfür zwei wichtige
Befehle: break und continue. Diese Befehle
ermöglichen eine präzise Kontrolle über den Programmfluss innerhalb von
Schleifen und sind besonders bei komplexen Szenarien unverzichtbar.
Der break-Befehl beendet die Ausführung einer Schleife
sofort und setzt den Programmfluss nach der Schleife fort. Er ist
besonders nützlich, wenn eine Bedingung erreicht wird, die das weitere
Durchlaufen der Schleife überflüssig macht.
#!/bin/bash
# Suche nach einer bestimmten Datei und breche ab, sobald sie gefunden wird
for file in *; do
if [ "$file" = "config.ini" ]; then
echo "Datei gefunden: $file"
break # Schleife beenden, sobald die Datei gefunden wurde
fi
done
echo "Suche beendet."#!/bin/bash
# Interaktive Eingabeschleife, die bei 'exit' beendet wird
while true; do
echo -n "Geben Sie einen Befehl ein (oder 'exit' zum Beenden): "
read command
if [ "$command" = "exit" ]; then
echo "Beende Programm..."
break
fi
echo "Führe aus: $command"
eval "$command"
done#!/bin/bash
# Verarbeite mehrere Dateien, breche bei Fehler ab
for file in *.csv; do
echo "Verarbeite $file..."
if ! grep -q "Header" "$file"; then
echo "Fehler: Ungültiges Dateiformat in $file. Header nicht gefunden."
echo "Abbruch der Verarbeitung."
break
fi
# Verarbeitung fortsetzen...
echo "$file erfolgreich verarbeitet."
doneDer continue-Befehl überspringt den Rest des aktuellen
Schleifendurchlaufs und beginnt sofort mit der nächsten Iteration. Dies
ist nützlich, wenn bestimmte Elemente übersprungen werden sollen, ohne
die gesamte Schleife zu beenden.
#!/bin/bash
# Verarbeite alle Textdateien, überspringe temporäre Dateien
for file in *.txt; do
# Überspringe Dateien, die mit 'temp_' beginnen
if [[ "$file" == temp_* ]]; then
echo "Überspringe temporäre Datei: $file"
continue
fi
echo "Verarbeite Datei: $file"
# Weitere Verarbeitung hier...
done#!/bin/bash
# Verarbeite mehrere Log-Dateien, überspringe defekte
for logfile in /var/log/*.log; do
# Überprüfe, ob die Datei lesbar ist
if [ ! -r "$logfile" ]; then
echo "Warnung: Keine Leseberechtigung für $logfile, überspringe..."
continue
fi
# Überprüfe, ob die Datei leer ist
if [ ! -s "$logfile" ]; then
echo "Hinweis: $logfile ist leer, überspringe..."
continue
fi
echo "Analysiere $logfile..."
# Analyse durchführen...
done#!/bin/bash
# Verarbeite Benutzerliste und filtere bestimmte Benutzer
while IFS=: read -r username password uid gid info home shell; do
# Überspringe System-Benutzer (UID < 1000)
if [ "$uid" -lt 1000 ]; then
continue
fi
# Überspringe Benutzer ohne Login-Shell
if [ "$shell" = "/usr/sbin/nologin" ] || [ "$shell" = "/bin/false" ]; then
continue
fi
echo "Regulärer Benutzer: $username (UID: $uid, Home: $home)"
done < /etc/passwdBei verschachtelten Schleifen beenden break und
continue standardmäßig nur die innerste Schleife. Mit einem
numerischen Argument kann man jedoch steuern, wie viele Schleifenebenen
betroffen sind.
#!/bin/bash
# Verschachtelte Schleifen mit mehrstufigem break
for directory in /home/*/; do
echo "Durchsuche Verzeichnis: $directory"
for file in "$directory"*.conf; do
if [ -f "$file" ] && grep -q "CRITICAL_ERROR" "$file"; then
echo "Kritischer Fehler in $file gefunden!"
echo "Breche die gesamte Suche ab."
break 2 # Beende sowohl die innere als auch die äußere Schleife
fi
done
echo "Verzeichnis $directory durchsucht."
done
echo "Durchsuchung abgeschlossen."#!/bin/bash
# Verschachtelte Schleifen mit mehrstufigem continue
for year in {2020..2023}; do
echo "Verarbeite Daten für Jahr $year"
for month in {01..12}; do
# Überspringe alle Monate des Jahres 2022 (springe zur nächsten Jahr-Iteration)
if [ "$year" -eq 2022 ]; then
echo "Jahr 2022 wird übersprungen."
continue 2
fi
echo " Verarbeite Monat $month/$year"
# Verarbeitung hier...
done
echo "Jahr $year abgeschlossen."
done#!/bin/bash
# Suche nach Dateien mit bestimmtem Muster, begrenzt auf 5 Treffer
found=0
max_results=5
find /var/log -type f -name "*.log" | while read -r file; do
if grep -q "ERROR" "$file"; then
echo "Fehler gefunden in: $file"
((found++))
if [ "$found" -ge "$max_results" ]; then
echo "Maximale Anzahl an Ergebnissen erreicht."
break
fi
fi
done#!/bin/bash
# Verarbeite mehrere Dateien, überspringe problematische, aber führe fort
success=0
failed=0
for file in data/*.csv; do
echo "Verarbeite $file..."
# Überprüfe Dateiformat
if ! head -1 "$file" | grep -q "^ID,Name,Date,Value$"; then
echo "Warnung: $file hat ein ungültiges Format, überspringe..."
((failed++))
continue
fi
# Versuche Daten zu importieren
if ! import_data "$file"; then
echo "Fehler beim Import von $file, überspringe..."
((failed++))
continue
fi
echo "$file erfolgreich verarbeitet."
((success++))
done
echo "Verarbeitung abgeschlossen: $success erfolgreich, $failed fehlgeschlagen."#!/bin/bash
# Warte auf einen Prozess mit Timeout
pid=$1
timeout=60
elapsed=0
while kill -0 "$pid" 2>/dev/null; do
if [ "$elapsed" -ge "$timeout" ]; then
echo "Timeout erreicht ($timeout Sekunden). Prozess läuft noch."
echo "Breche Warteschleife ab."
break
fi
echo "Warte auf Prozess $pid... ($elapsed/$timeout s)"
sleep 5
elapsed=$((elapsed + 5))
done
if [ "$elapsed" -lt "$timeout" ]; then
echo "Prozess $pid wurde innerhalb des Timeouts beendet."
fi#!/bin/bash
# Kontrollierte Schleifenbeendigung durch Signale
cleanup() {
echo "Signal empfangen, beende Schleife sauber..."
break_loop=true
}
# Fange SIGINT (Ctrl+C) und SIGTERM
trap cleanup SIGINT SIGTERM
break_loop=false
while [ "$break_loop" = false ]; do
echo "Verarbeite..."
sleep 1
# Hier könnte ein anderer Grund sein, die Schleife zu beenden
if [ -f "stop_processing" ]; then
echo "Stopp-Datei gefunden."
break
fi
done
echo "Schleife beendet, führe Aufräumarbeiten durch..."
# Aufräumarbeiten hier...#!/bin/bash
# Verarbeite Daten, passe Verhalten basierend auf Erfolgsrate an
total=0
success=0
error_threshold=0.3 # 30% Fehlerrate
for file in input/*.dat; do
((total++))
if process_file "$file"; then
((success++))
else
# Berechne aktuelle Fehlerrate
error_rate=$(echo "scale=2; ($total-$success)/$total" | bc)
echo "Aktuelle Fehlerrate: $error_rate (Schwellwert: $error_threshold)"
# Wenn Fehlerrate zu hoch, breche ab
if (( $(echo "$error_rate > $error_threshold" | bc -l) )); then
echo "Fehlerrate überschreitet Schwellwert. Abbruch der Verarbeitung."
break
fi
fi
# Nach jeweils 10 Dateien Status ausgeben
if [ $((total % 10)) -eq 0 ]; then
echo "Fortschritt: $total Dateien verarbeitet, Erfolgsrate: $(echo "scale=2; $success/$total" | bc)"
fi
doneKlar dokumentieren, warum eine Schleife vorzeitig beendet wird:
for file in "$directory"/*; do
# Breche ab, wenn eine bestimmte Bedingung erfüllt ist
if [ condition ]; then
echo "Abbruch wegen: <spezifischer Grund>"
break
fi
doneVerwende continue für erwartete Ausnahmen, break für unerwartete Zustände:
while read -r line; do
# Überspringe Kommentarzeilen (erwartete Ausnahme)
if [[ "$line" == \#* ]]; then
continue
fi
# Breche bei schwerwiegendem Fehler ab (unerwarteter Zustand)
if ! validate_line "$line"; then
echo "Ungültiges Datenformat. Abbruch." >&2
break
fi
done < "$input_file"Vermeide zu viele break/continue-Anweisungen in einer Schleife:
# Besser: Logikstruktur verbessern statt viele continue-Anweisungen
for item in "${items[@]}"; do
# Kombinierte Bedingung statt mehrerer continue
if [[ "$item" == valid_* && -f "$item" && ! -L "$item" ]]; then
process_item "$item"
fi
doneVorsicht bei break/continue in Funktionen innerhalb von Schleifen:
# Break in einer Funktion bricht nur die Schleife in der Funktion ab,
# nicht die aufrufende Schleife
process_item() {
local item="$1"
for part in $item; do
# Dieses break beendet nur die innere Schleife
if [ condition ]; then break; fi
done
}
for item in "${items[@]}"; do
process_item "$item"
# Die äußere Schleife wird nicht durch break in process_item beendet
doneKlar definierte Exit-Strategien für komplexe Schleifen:
# Klare Bedingungen für Schleifenabbruch definieren
max_iterations=100
iterations=0
while true; do
((iterations++))
# Verschiedene Exit-Bedingungen
if [ "$iterations" -gt "$max_iterations" ]; then
echo "Maximale Anzahl an Iterationen erreicht."
break
fi
if [ -f "abort_flag" ]; then
echo "Externe Abbruch-Flagge gefunden."
break
fi
if ! process_step "$iterations"; then
echo "Verarbeitungsfehler aufgetreten."
break
fi
doneVergessen von break bei true-while-Schleifen:
# Problem: Keine Abbruchbedingung
while true; do
# Wenn hier kein break ist, entsteht eine Endlosschleife
done
# Lösung: Immer eine Abbruchbedingung einbauen
while true; do
# Verarbeitung
if [ condition ]; then
break
fi
doneFalsche Schleifenebene bei verschachtelten Schleifen:
# Problem: break beendet nur die innere Schleife
for i in {1..10}; do
for j in {1..10}; do
if [ condition ]; then
break # Beendet nur die innere j-Schleife
fi
done
# Ausführung geht hier weiter, auch wenn condition in der inneren Schleife true war
done
# Lösung: Verwende break 2 oder eine Flag-Variable
found=false
for i in {1..10}; do
for j in {1..10}; do
if [ condition ]; then
found=true
break # Beendet die innere Schleife
fi
done
# Prüfe Flag-Variable für äußere Schleife
if [ "$found" = true ]; then
break
fi
donecontinue in der letzten Anweisung einer Schleife:
# Überflüssiges continue am Ende einer Schleife
for item in "${items[@]}"; do
process_item "$item"
continue # Unnötig, da die Schleife ohnehin zur nächsten Iteration übergehen würde
done
# Besser: Einfach weglassen
for item in "${items[@]}"; do
process_item "$item"
doneÜbersehen des Kontextwechsels bei Pipelines:
# Problem: break in einer Pipeline wirkt nur auf die Subshell
found=0
cat file.txt | while read -r line; do
((found++))
if [ "$found" -eq 10 ]; then
break # Beendet nur die Schleife in der Subshell
fi
done
echo "Gefunden: $found" # Wird immer 0 ausgeben!
# Lösung: Direkte Eingabeumleitung verwenden
found=0
while read -r line; do
((found++))
if [ "$found" -eq 10 ]; then
break # Beendet die Schleife in der aktuellen Shell
fi
done < file.txt
echo "Gefunden: $found" # Gibt korrekt die Anzahl ausDie Befehle break und continue sind
mächtige Werkzeuge zur Steuerung des Programmflusses in Schleifen. Mit
ihnen können Sie Ihre Shell-Skripte effizienter gestalten und präzise
auf verschiedene Bedingungen reagieren. Besonders in komplexen Skripten
mit verschachtelten Schleifen oder umfangreicher Fehlerbehandlung sind
diese Kontrollmechanismen unverzichtbar.
Während if-Anweisungen für einfache bedingte
Ausführungen geeignet sind, werden sie bei mehreren Verzweigungen
schnell unübersichtlich. Die Bash bietet mit dem
case-Befehl eine elegante Alternative für Situationen, in
denen ein Wert mit verschiedenen Mustern verglichen werden soll. Der
case-Befehl ist besonders nützlich für Menüsysteme, die
Verarbeitung von Befehlszeilenparametern und für Skripte, die
unterschiedliche Aktionen basierend auf Benutzereingaben ausführen.
case WERT in
MUSTER1)
BEFEHLE1
;;
MUSTER2)
BEFEHLE2
;;
MUSTER3 | MUSTER4) # Mehrere Muster mit | kombinieren
BEFEHLE3
;;
*) # Standardfall (ähnlich wie else)
STANDARD_BEFEHLE
;;
esacDer case-Befehl vergleicht den angegebenen
WERT nacheinander mit jedem MUSTER. Wenn ein
Muster übereinstimmt, werden die zugehörigen Befehle ausgeführt, und die
Ausführung wird nach dem entsprechenden ;; fortgesetzt. Das
Schlüsselwort esac (umgekehrtes “case”) schließt den
case-Block ab.
#!/bin/bash
# Auswertung des ersten Parameters
case "$1" in
start)
echo "Starte den Dienst..."
service myservice start
;;
stop)
echo "Stoppe den Dienst..."
service myservice stop
;;
restart)
echo "Starte den Dienst neu..."
service myservice restart
;;
status)
echo "Status des Dienstes:"
service myservice status
;;
*)
echo "Verwendung: $0 {start|stop|restart|status}"
exit 1
;;
esacDer case-Befehl unterstützt verschiedene Arten von
Mustern:
Einfache Zeichenketten:
case "$input" in
yes)
# Wenn $input genau "yes" ist
;;
esacWildcards und Globbing:
case "$filename" in
*.jpg | *.jpeg)
# Wenn $filename auf .jpg oder .jpeg endet
echo "JPEG-Bild gefunden"
;;
*.png)
# Wenn $filename auf .png endet
echo "PNG-Bild gefunden"
;;
*.txt)
# Wenn $filename auf .txt endet
echo "Textdatei gefunden"
;;
esacZeichenklassen:
case "$key" in
[yY] | [yY][eE][sS])
# Wenn $key "y", "Y", "yes", "YES", "Yes", etc. ist
echo "Sie haben zugestimmt"
;;
[nN] | [nN][oO])
# Wenn $key "n", "N", "no", "NO", "No", etc. ist
echo "Sie haben abgelehnt"
;;
esacBereichsmuster:
case "$char" in
[a-z])
echo "Kleinbuchstabe"
;;
[A-Z])
echo "Großbuchstabe"
;;
[0-9])
echo "Ziffer"
;;
esac#!/bin/bash
while true; do
echo -e "\nSystem-Administration"
echo "======================="
echo "1. Systemstatus anzeigen"
echo "2. Dienste verwalten"
echo "3. Benutzer verwalten"
echo "4. Backup erstellen"
echo "0. Beenden"
echo -n "Wählen Sie eine Option (0-4): "
read choice
case "$choice" in
1)
echo -e "\nSystemstatus:"
echo "-------------"
uptime
df -h
free -m
;;
2)
echo -e "\nDienste:"
echo "--------"
echo "a) Apache starten"
echo "b) Apache stoppen"
echo "c) MySQL starten"
echo "d) MySQL stoppen"
echo -n "Wählen Sie einen Dienst (a-d): "
read service_choice
case "$service_choice" in
[aA])
echo "Starte Apache..."
systemctl start apache2
;;
[bB])
echo "Stoppe Apache..."
systemctl stop apache2
;;
[cC])
echo "Starte MySQL..."
systemctl start mysql
;;
[dD])
echo "Stoppe MySQL..."
systemctl stop mysql
;;
*)
echo "Ungültige Auswahl!"
;;
esac
;;
3)
echo -e "\nBenutzerverwaltung:"
echo "------------------"
# Benutzerverwaltung-Code hier...
;;
4)
echo -e "\nBackup erstellen:"
echo "----------------"
# Backup-Code hier...
;;
0)
echo "Programm wird beendet."
exit 0
;;
*)
echo "Ungültige Auswahl! Bitte 0-4 eingeben."
;;
esac
echo -e "\nDrücken Sie Enter, um fortzufahren..."
read
done#!/bin/bash
# Verarbeitet eine Liste von Dateien basierend auf ihren Erweiterungen
process_files() {
for file in "$@"; do
if [ ! -f "$file" ]; then
echo "Warnung: $file ist keine Datei oder existiert nicht."
continue
fi
# Erweiterung extrahieren
ext="${file##*.}"
case "${ext,,}" in # ${ext,,} konvertiert zu Kleinbuchstaben
jpg|jpeg|png|gif|svg)
echo "Verarbeite Bild: $file"
process_image "$file"
;;
mp3|wav|flac|ogg)
echo "Verarbeite Audio: $file"
process_audio "$file"
;;
mp4|avi|mkv|mov)
echo "Verarbeite Video: $file"
process_video "$file"
;;
txt|md|csv)
echo "Verarbeite Text: $file"
process_text "$file"
;;
pdf|doc|docx|odt)
echo "Verarbeite Dokument: $file"
process_document "$file"
;;
*)
echo "Unbekannter Dateityp für $file"
;;
esac
done
}
# Dummy-Funktionen für die Verarbeitung
process_image() { echo " Bild $1 verarbeitet"; }
process_audio() { echo " Audio $1 verarbeitet"; }
process_video() { echo " Video $1 verarbeitet"; }
process_text() { echo " Text $1 verarbeitet"; }
process_document() { echo " Dokument $1 verarbeitet"; }
# Beispielaufruf
process_files sample.jpg document.pdf unknown.xyz music.mp3#!/bin/bash
# Standardwerte
verbose=false
output_file=""
count=1
# Verarbeitung der Optionen
while [ $# -gt 0 ]; do
case "$1" in
-v|--verbose)
verbose=true
shift
;;
-o|--output)
if [ -n "$2" ]; then
output_file="$2"
shift 2
else
echo "Fehler: Option --output benötigt ein Argument." >&2
exit 1
fi
;;
-c|--count)
if [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]]; then
count="$2"
shift 2
else
echo "Fehler: Option --count benötigt eine positive Ganzzahl." >&2
exit 1
fi
;;
-h|--help)
echo "Verwendung: $0 [OPTIONEN] DATEI"
echo "Optionen:"
echo " -v, --verbose Ausführliche Ausgabe"
echo " -o, --output DATEI Ausgabe in DATEI schreiben"
echo " -c, --count ZAHL Anzahl der Wiederholungen (Standard: 1)"
echo " -h, --help Diese Hilfe anzeigen"
exit 0
;;
-*)
echo "Fehler: Unbekannte Option: $1" >&2
echo "Verwenden Sie --help für Hilfe." >&2
exit 1
;;
*)
# Kein Options-Argument mehr, behandle als Dateiname
input_file="$1"
shift
;;
esac
done
# Prüfe, ob eine Eingabedatei angegeben wurde
if [ -z "$input_file" ]; then
echo "Fehler: Keine Eingabedatei angegeben." >&2
echo "Verwenden Sie --help für Hilfe." >&2
exit 1
fi
# Ausgabe der Einstellungen
$verbose && echo "Einstellungen:"
$verbose && echo "- Eingabedatei: $input_file"
$verbose && echo "- Ausgabedatei: ${output_file:-Standard-Ausgabe}"
$verbose && echo "- Anzahl: $count"
# Hier folgt die eigentliche Verarbeitung...Ab Bash 4.0 unterstützt der case-Befehl mit
;& einen “Fall-Through”-Mechanismus, der die Ausführung
mit dem nächsten Muster fortsetzt, ähnlich wie bei switch
ohne break in anderen Sprachen:
#!/bin/bash
echo -n "Geben Sie eine Zahl ein (1-3): "
read num
case $num in
1)
echo "Eins"
;& # Fall-Through zum nächsten Muster
2)
echo "Kleiner oder gleich Zwei"
;& # Fall-Through zum nächsten Muster
3)
echo "Kleiner oder gleich Drei"
;;
*)
echo "Größer als Drei oder keine Zahl"
;;
esacBei Eingabe von 1 würden alle drei Meldungen
ausgegeben.
Ab Bash 4.0 gibt es auch ;;&, das die Prüfung mit
dem nächsten Muster fortsetzt, statt direkt zum nächsten Muster zu
springen:
#!/bin/bash
echo -n "Geben Sie ein Zeichen ein: "
read char
case $char in
[0-9])
echo "Das Zeichen ist eine Ziffer."
;;& # Prüfe nächstes Muster
[0-9a-fA-F])
echo "Das Zeichen ist eine hexadezimale Ziffer."
;;& # Prüfe nächstes Muster
[a-zA-Z])
echo "Das Zeichen ist ein Buchstabe."
;;
*)
echo "Das Zeichen ist weder eine Ziffer noch ein Buchstabe."
;;
esacHier werden nur passende Muster ausgeführt. Eine Eingabe von
7 würde “Das Zeichen ist eine Ziffer.” und “Das Zeichen ist
eine hexadezimale Ziffer.” ausgeben, aber nicht “Das Zeichen ist ein
Buchstabe.”
Klare Strukturierung: Achten Sie auf eine konsistente Einrückung und Formatierung, um die Lesbarkeit zu verbessern.
case "$input" in
yes)
# Eingerückte Befehle
command1
command2
;;
no)
# Eingerückte Befehle
command3
command4
;;
esacDer Fallback-Fall: Implementieren Sie immer
einen Fallback-Fall (*), um unerwartete Eingaben
abzufangen.
case "$choice" in
1) echo "Option 1";;
2) echo "Option 2";;
*) echo "Ungültige Option. Bitte 1 oder 2 wählen.";;
esacVariablen in Anführungszeichen setzen: Dies verhindert Probleme mit Leerzeichen und Sonderzeichen.
case "$variable" in # Gut
# ...
esac
case $variable in # Kann bei Leerzeichen Probleme verursachen
# ...
esacMehrere Muster zusammenfassen:
case "$option" in
-h|--help|-\?)
show_help
;;
-v|--version)
show_version
;;
esacVermeiden Sie übermäßig komplexe Muster: Teilen
Sie komplexe Logik bei Bedarf in separate Funktionen oder verschachtelte
case-Anweisungen auf.
#!/bin/bash
echo "Wählen Sie eine Option:"
echo "1) Dateien auflisten"
echo "2) Aktuelles Verzeichnis anzeigen"
echo "3) Datum und Uhrzeit anzeigen"
echo "q) Beenden"
read -p "Ihre Wahl: " option
case "$option" in
1)
ls -la
;;
2)
pwd
;;
3)
date
;;
[qQ])
echo "Programm wird beendet."
exit 0
;;
*)
echo "Ungültige Option!"
;;
esac#!/bin/bash
# Hilfe-Funktion
show_help() {
echo "Verwendung: $0 BEFEHL [ARGUMENTE]"
echo
echo "Befehle:"
echo " backup SOURCE DEST Backup von SOURCE nach DEST erstellen"
echo " restore BACKUP DIR Backup in Verzeichnis DIR wiederherstellen"
echo " list [PATTERN] Verfügbare Backups auflisten, optional nach PATTERN filtern"
echo " help Diese Hilfe anzeigen"
}
# Hauptlogik
case "$1" in
backup)
if [ -z "$2" ] || [ -z "$3" ]; then
echo "Fehler: backup benötigt SOURCE und DEST Parameter."
show_help
exit 1
fi
echo "Erstelle Backup von $2 nach $3..."
# Backup-Code hier
;;
restore)
if [ -z "$2" ] || [ -z "$3" ]; then
echo "Fehler: restore benötigt BACKUP und DIR Parameter."
show_help
exit 1
fi
echo "Stelle Backup $2 in Verzeichnis $3 wieder her..."
# Restore-Code hier
;;
list)
echo "Verfügbare Backups:"
# List-Code hier
;;
help|--help|-h)
show_help
;;
"")
echo "Fehler: Kein Befehl angegeben."
show_help
exit 1
;;
*)
echo "Fehler: Unbekannter Befehl: $1"
show_help
exit 1
;;
esac#!/bin/bash
# Ein vereinfachtes Init-Skript
SERVICE_NAME="myservice"
DAEMON="/usr/bin/myservice"
PIDFILE="/var/run/myservice.pid"
case "$1" in
start)
echo "Starte $SERVICE_NAME..."
if [ -f "$PIDFILE" ]; then
echo "$SERVICE_NAME läuft bereits (PID: $(cat $PIDFILE))"
exit 1
fi
$DAEMON &
echo $! > "$PIDFILE"
;;
stop)
echo "Stoppe $SERVICE_NAME..."
if [ ! -f "$PIDFILE" ]; then
echo "$SERVICE_NAME läuft nicht."
exit 1
fi
kill $(cat "$PIDFILE") && rm "$PIDFILE"
;;
restart)
$0 stop
sleep 2
$0 start
;;
status)
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
echo "$SERVICE_NAME läuft (PID: $(cat $PIDFILE))"
else
echo "$SERVICE_NAME läuft nicht."
[ -f "$PIDFILE" ] && rm "$PIDFILE"
fi
;;
*)
echo "Verwendung: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit 0Der case-Befehl ist ein mächtiges Werkzeug für
Shell-Skripte, das Mehrfachverzweigungen elegant und lesbar
implementiert. Im Vergleich zu verschachtelten
if-Anweisungen bietet case eine klarere
Struktur und bessere Lesbarkeit, insbesondere wenn viele verschiedene
Bedingungen verglichen werden müssen. Die Möglichkeit, Muster mit
Wildcards und regulären Ausdrücken zu verwenden, macht den
case-Befehl besonders vielseitig und leistungsfähig für
eine Vielzahl von Anwendungsfällen in der Shell-Programmierung.
Die exit-Anweisung ist ein fundamentaler Bestandteil der
Shell-Programmierung, der es ermöglicht, ein Skript zu beenden und einen
Statuscode an das aufrufende Programm zurückzugeben. Dieser Mechanismus
ist entscheidend für die Kommunikation zwischen Skripten und anderen
Programmen, für die Fehlerbehandlung und für die Implementierung
robuster Programmflüsse.
Die Syntax der exit-Anweisung ist denkbar einfach:
exit [n]wobei [n] ein optionaler Statuscode (eine Ganzzahl) ist.
Wenn kein Statuscode angegeben wird, beendet die
exit-Anweisung das Skript mit dem Statuscode der letzten
ausgeführten Anweisung.
In der Unix/Linux-Welt gilt folgende Konvention für Exit-Statuscodes:
Es gibt einige allgemein akzeptierte Konventionen für bestimmte Statuscodes:
| Statuscode | Typische Bedeutung |
|---|---|
| 0 | Erfolg |
| 1 | Allgemeiner Fehler |
| 2 | Falsche Verwendung des Shell-Builtin-Befehls |
| 126 | Befehl gefunden, aber nicht ausführbar |
| 127 | Befehl nicht gefunden |
| 128 | Ungültiges Argument für exit |
| 128+n | Vom Signal n (z.B. 130 = SIGINT) beendet |
| 255* | Exit-Status außerhalb des Bereichs |
*Hinweis: Technisch gesehen werden Statuscodes > 255 auf den Rest modulo 256 reduziert, daher ist 255 der höchste effektive Statuscode.
#!/bin/bash
# Prüfe, ob eine Datei existiert
if [ ! -f "/etc/hosts" ]; then
echo "Fehler: Die Datei /etc/hosts existiert nicht!" >&2
exit 1
fi
# Skript wird nur ausgeführt, wenn die Datei existiert
echo "Die Datei /etc/hosts existiert."
# Weitere Verarbeitung...
# Erfolgreicher Abschluss
exit 0Der Exit-Status des zuletzt ausgeführten Befehls wird in der
speziellen Variablen $? gespeichert und kann in Skripten
oder auf der Kommandozeile verwendet werden:
grep "pattern" file.txt
echo "Exit-Status von grep: $?"Wenn “pattern” in “file.txt” gefunden wird, gibt grep den Exit-Status 0 zurück; andernfalls gibt es einen Wert ungleich 0 zurück (normalerweise 1).
Die if-Anweisung prüft standardmäßig den Exit-Status
eines Befehls:
if grep -q "wichtigerEintrag" /etc/config.conf; then
echo "Eintrag gefunden, fahre fort..."
else
echo "Eintrag nicht gefunden, beende..." >&2
exit 1
fiWenn exit innerhalb einer Funktion aufgerufen wird,
beendet es das gesamte Skript, nicht nur die Funktion:
#!/bin/bash
check_prerequisites() {
if [ ! -f "required_file.txt" ]; then
echo "Fehler: required_file.txt nicht gefunden!" >&2
exit 1 # Beendet das gesamte Skript
fi
echo "Voraussetzungen erfüllt."
}
echo "Starte Skript..."
check_prerequisites
echo "Skript wird fortgesetzt." # Dieser Code wird nicht ausgeführt, wenn required_file.txt nicht existiertUm eine Funktion ohne Beendigung des gesamten Skripts zu verlassen,
verwenden Sie return statt exit:
#!/bin/bash
check_prerequisites() {
if [ ! -f "required_file.txt" ]; then
echo "Fehler: required_file.txt nicht gefunden!" >&2
return 1 # Verlässt nur die Funktion
fi
echo "Voraussetzungen erfüllt."
return 0
}
echo "Starte Skript..."
if ! check_prerequisites; then
echo "Voraussetzungen nicht erfüllt, beende Skript."
exit 1
fi
echo "Skript wird fortgesetzt."#!/bin/bash
# Definiere Fehlercodes
E_ARGS=65 # Falsche Anzahl an Argumenten
E_NOFILE=66 # Datei nicht gefunden
E_NOPERM=67 # Keine Berechtigung
E_FORMAT=68 # Falsches Format
E_NETWORK=69 # Netzwerkfehler
# Prüfe Befehlszeilenargumente
if [ $# -ne 2 ]; then
echo "Fehler: Falsche Anzahl an Argumenten." >&2
echo "Verwendung: $0 QUELLDATEI ZIELDATEI" >&2
exit $E_ARGS
fi
source_file="$1"
target_file="$2"
# Prüfe, ob die Quelldatei existiert
if [ ! -f "$source_file" ]; then
echo "Fehler: Quelldatei '$source_file' existiert nicht." >&2
exit $E_NOFILE
fi
# Prüfe Leserechte für die Quelldatei
if [ ! -r "$source_file" ]; then
echo "Fehler: Keine Leseberechtigung für '$source_file'." >&2
exit $E_NOPERM
fi
# Prüfe, ob die Quelldatei ein gültiges Format hat
if ! validate_format "$source_file"; then
echo "Fehler: '$source_file' hat ein ungültiges Format." >&2
exit $E_FORMAT
fi
# Versuche, die Datei zu übertragen
if ! transfer_file "$source_file" "$target_file"; then
echo "Fehler: Dateiübertragung fehlgeschlagen." >&2
exit $E_NETWORK
fi
echo "Datei erfolgreich übertragen."
exit 0Die Operatoren && (AND) und || (OR)
nutzen den Exit-Status zur bedingten Ausführung:
# Führe command2 nur aus, wenn command1 erfolgreich war (Exit-Status 0)
command1 && command2
# Führe command2 nur aus, wenn command1 nicht erfolgreich war (Exit-Status ungleich 0)
command1 || command2Beispiel für die Verkettung von Befehlen mit bedingter Ausführung:
#!/bin/bash
# Erstelle Verzeichnis und wechsle hinein, beende bei Fehler
mkdir -p /tmp/workspace && cd /tmp/workspace || {
echo "Fehler: Konnte Arbeitsverzeichnis nicht erstellen oder betreten." >&2
exit 1
}
# Lade Konfiguration herunter, beende bei Fehler
wget -q https://example.com/config.ini || {
echo "Fehler: Konnte Konfigurationsdatei nicht herunterladen." >&2
exit 1
}
# Führe Installation durch
./install.shDie exit-Anweisung kann in Kombination mit der
trap-Anweisung verwendet werden, um beim Beenden des
Skripts (unabhängig vom Grund) aufzuräumen:
#!/bin/bash
# Temporäre Datei erstellen
TEMP_FILE=$(mktemp)
# Aufräumfunktion definieren
cleanup() {
echo "Räume auf..."
rm -f "$TEMP_FILE"
}
# trap registrieren, um die Aufräumfunktion beim Beenden auszuführen
trap cleanup EXIT
# Skript-Logik
echo "Arbeite mit temporärer Datei: $TEMP_FILE"
# ... weitere Verarbeitung ...
# Das Skript beendet sich hier normal
exit 0
# Die cleanup-Funktion wird automatisch aufgerufen, bevor das Skript beendet wirdMit trap können Sie auch spezifische Fehlerbehandlungen
für verschiedene Signale definieren:
#!/bin/bash
# Fehlerbehandlungsfunktion
handle_error() {
local exit_code=$?
local line_number=$1
echo "Fehler in Zeile $line_number: Befehl mit Exit-Code $exit_code fehlgeschlagen" >&2
exit $exit_code
}
# Registriere trap für ERR-Signal mit Zeilennummer
trap 'handle_error $LINENO' ERR
# Skript-Logik
echo "Starte komplexe Operation..."
# Diese Anweisung wird fehlschlagen und die Fehlerbehandlung auslösen
non_existent_command # Fehler: Befehl nicht gefunden
echo "Diese Zeile wird nie erreicht."Ein eleganter Ansatz zur Fehlerbehandlung ist die Verwendung einer Statuscode-Variable, die den Exit-Status verfolgt:
#!/bin/bash
# Initialisiere Exit-Status
EXIT_CODE=0
# Führe verschiedene Aufgaben aus und aktualisiere EXIT_CODE bei Fehlern
echo "Führe Aufgabe 1 aus..."
task1
if [ $? -ne 0 ]; then
echo "Warnung: Aufgabe 1 fehlgeschlagen." >&2
EXIT_CODE=1
fi
echo "Führe Aufgabe 2 aus..."
task2
if [ $? -ne 0 ]; then
echo "Warnung: Aufgabe 2 fehlgeschlagen." >&2
EXIT_CODE=1
fi
echo "Führe kritische Aufgabe 3 aus..."
critical_task3
if [ $? -ne 0 ]; then
echo "Fehler: Kritische Aufgabe 3 fehlgeschlagen!" >&2
exit 2 # Sofortiger Abbruch bei kritischen Fehlern
fi
# Beende mit gesammeltem Exit-Status
echo "Alle Aufgaben abgeschlossen."
exit $EXIT_CODEMit set -e wird das Skript automatisch beendet, wenn ein
Befehl fehlschlägt (Exit-Status ungleich 0):
#!/bin/bash
set -e # Aktiviere automatisches Beenden bei Fehler
# Diese Befehle werden nacheinander ausgeführt, aber das Skript stoppt beim ersten Fehler
mkdir /tmp/testdir
cd /tmp/testdir
touch testfile
non_existent_command # Hier stoppt das Skript automatisch
echo "Diese Zeile wird nie erreicht."Um bestimmte Befehle von dieser Regel auszunehmen, können Sie
|| true anhängen:
#!/bin/bash
set -e
# Dieser Befehl darf fehlschlagen, ohne das Skript zu beenden
grep "pattern" file.txt || true
# Skript wird fortgesetzt, auch wenn grep fehlschlägt
echo "Skript läuft weiter..."Sie können trap verwenden, um auf Signale wie SIGINT
(Ctrl+C) oder SIGTERM zu reagieren und das Skript kontrolliert zu
beenden:
#!/bin/bash
# Signalbehandlungsfunktion
handle_signal() {
echo -e "\nSignal empfangen. Führe sauberes Beenden durch..."
# Aufräumarbeiten hier
exit 130 # 128 + 2 (SIGINT)
}
# Fange SIGINT (Ctrl+C) und SIGTERM
trap handle_signal SIGINT SIGTERM
echo "Langläufiger Prozess gestartet. Drücken Sie Ctrl+C zum Abbrechen."
while true; do
echo -n "."
sleep 1
doneKonsistente Exit-Codes verwenden: Definieren Sie am Anfang des Skripts bedeutungsvolle Exit-Codes und verwenden Sie diese durchgängig.
#!/bin/bash
# Definiere Exit-Codes
SUCCESS=0
ERR_USAGE=1
ERR_NO_INPUT=2
ERR_INVALID_INPUT=3
ERR_PROCESSING=4
# Verwendung im Skript
if [ $# -eq 0 ]; then
echo "Fehler: Keine Eingabedatei angegeben." >&2
exit $ERR_NO_INPUT
fiFehler auf STDERR ausgeben: Verwenden Sie die
Umleitung auf den Fehlerkanal (>&2) für
Fehlermeldungen.
if [ ! -f "$file" ]; then
echo "Fehler: Datei nicht gefunden: $file" >&2
exit 1
fiHilfreiche Fehlermeldungen: Geben Sie aussagekräftige Fehlermeldungen aus, die dem Benutzer helfen, das Problem zu verstehen und zu beheben.
if [ ! -x "$command" ]; then
echo "Fehler: '$command' ist nicht ausführbar. Bitte Berechtigungen prüfen (chmod +x $command)." >&2
exit 1
fiImmer explizit am Ende beenden: Beenden Sie das
Skript am Ende explizit mit exit 0, um einen erfolgreichen
Abschluss zu signalisieren.
# Verarbeitung...
echo "Alle Aufgaben erfolgreich abgeschlossen."
exit 0 # Explizites Ende mit ErfolgAufräumen vor dem Beenden: Stellen Sie sicher, dass temporäre Dateien und Ressourcen vor dem Beenden freigegeben werden.
cleanup_and_exit() {
rm -f "$TEMP_FILE"
exit "$1"
}
# Verwendung
if [ error_condition ]; then
cleanup_and_exit 1
fi
# Erfolgreicher Abschluss
cleanup_and_exit 0Vergessen, den Exit-Status zu prüfen:
# Problematisch: Der Exit-Status wird nicht geprüft
rsync -av source/ destination/
# Besser: Überprüfe den Exit-Status
if ! rsync -av source/ destination/; then
echo "Fehler: Synchronisation fehlgeschlagen!" >&2
exit 1
fiÜberschreiben des Exit-Status durch andere Befehle:
# Problematisch: Der Exit-Status von command wird überschrieben
command
echo "Command ausgeführt." # Überschreibt $?
exit $? # Gibt immer den Exit-Status von 'echo' zurück (fast immer 0)
# Besser: Exit-Status speichern
command
status=$? # Speichere Exit-Status sofort
echo "Command ausgeführt."
exit $status # Verwende den gespeicherten StatusZu frühes Beenden bei nicht-kritischen Fehlern:
# Problematisch: Skript endet bei jedem Fehler
command1 || exit 1
command2 || exit 1
command3 || exit 1
# Besser: Sammle Fehler, beende nur wenn nötig
errors=0
command1 || ((errors++))
command2 || ((errors++))
command3 || ((errors++))
if [ $errors -gt 0 ]; then
echo "Es sind $errors Fehler aufgetreten." >&2
exit 1
fiIgnorieren von Exit-Status in Pipelines:
# Problematisch: Nur der Exit-Status des letzten Befehls in der Pipeline wird geprüft
grep "pattern" file.txt | sort | uniq > results.txt
# Besser mit Bash 4.1+: PIPEFAIL aktivieren
set -o pipefail
if ! grep "pattern" file.txt | sort | uniq > results.txt; then
echo "Fehler in der Pipeline!" >&2
exit 1
fiDie exit-Anweisung ist ein mächtiges Werkzeug in der
Shell-Programmierung, das für die Kontrolle des Programmflusses und die
Kommunikation mit dem aufrufenden Programm unerlässlich ist. Durch die
konsequente und durchdachte Verwendung von Exit-Statuscodes können Sie
robuste und zuverlässige Skripte erstellen, die sowohl menschlichen
Benutzern als auch anderen Programmen klare Rückmeldungen über den
Erfolg oder Misserfolg ihrer Ausführung geben.