Skip to main content

Bash Scripting

Inhaltsverzeichnis

  1. Grundlagen
  2. Shebang und Interpreter
  3. Variablen
  4. Benutzereingaben
  5. Bedingungen (if/else)
  6. Schleifen (for/while/until)
  7. Funktionen
  8. Arrays
  9. String-Manipulation
  10. Mathematische Operationen
  11. Dateien und Verzeichnisse
  12. Input/Output und Redirection
  13. Error Handling
  14. Reguläre Ausdrücke
  15. Netzwerk und Ports
  16. Prozesse und Jobs
  17. Debugging
  18. Best Practices
  19. Praktische Beispiele

1. Grundlagen

Was ist ein Bash-Script?

Ein Bash-Script ist eine Textdatei mit Befehlen, die von der Bash-Shell ausgeführt werden. Scripts automatisieren wiederkehrende Aufgaben und kombinieren mehrere Befehle.

Warum Bash-Scripts?

  • Automatisierung: Wiederkehrende Aufgaben automatisch ausführen
  • Systemverwaltung: Server-Management, Backups, Monitoring
  • Kombinieren von Tools: Unix-Tools miteinander verknüpfen
  • Portabilität: Läuft auf fast allen Unix/Linux-Systemen

Erstes Script erstellen

1. Datei erstellen:

nano mein-script.sh

2. Inhalt schreiben:

#!/bin/bash
echo "Hallo Welt!"

3. Ausführbar machen:

chmod +x mein-script.sh

4. Ausführen:

./mein-script.sh

Ausführungsmethoden

# Methode 1: Direkt (benötigt chmod +x)
./script.sh

# Methode 2: Mit Interpreter
bash script.sh

# Methode 3: Source (läuft im aktuellen Shell-Kontext)
source script.sh
# oder
. script.sh

Unterschied source vs. ./:

# ./script.sh
# - Läuft in Sub-Shell
# - Variablen sind nicht im Parent verfügbar
# - Exit beendet nur das Script

# source script.sh
# - Läuft in aktueller Shell
# - Variablen bleiben verfügbar
# - Exit beendet die ganze Shell!

2. Shebang und Interpreter

Was ist der Shebang?

Die erste Zeile eines Scripts, die angibt, welcher Interpreter verwendet werden soll.

#!/bin/bash

Verschiedene Shebangs

#!/bin/bash
# Standard Bash

#!/bin/sh
# POSIX-kompatible Shell (meist dash auf Ubuntu)

#!/usr/bin/env bash
# Bash über PATH finden (portabler)

#!/usr/bin/python3
# Python-Script

#!/usr/bin/perl
# Perl-Script

Wann welchen Shebang?

Verwende #!/bin/bash wenn:

  • Du Bash-spezifische Features nutzt (Arrays, [[...]], etc.)
  • Das Script nur auf Systemen mit Bash läuft

Verwende #!/bin/sh wenn:

  • Maximale Portabilität gewünscht
  • Nur POSIX-Features verwendet werden
  • Script auf embedded Systems laufen soll

Verwende #!/usr/bin/env bash wenn:

  • Bash an verschiedenen Orten sein könnte
  • Maximale Portabilität bei Bash-Scripts
  • Empfohlen für moderne Scripts

Interpreter-Optionen

#!/bin/bash -x
# Debug-Modus (zeigt jeden Befehl)

#!/bin/bash -e
# Exit bei Fehler

#!/bin/bash -u
# Exit bei undefined Variablen

#!/bin/bash -euo pipefail
# Kombination (sehr strikt, empfohlen!)

3. Variablen

Variablen definieren

# Einfache Zuweisung (KEINE Leerzeichen um =!)
NAME="Max"
ALTER=25
DATEI="/tmp/test.txt"

# Ausgabe
echo $NAME
echo ${NAME}  # Besser, eindeutiger

Wichtig: Keine Leerzeichen um =!

# FALSCH:
NAME = "Max"

# RICHTIG:
NAME="Max"

Variablen verwenden

NAME="Max"

# Standard
echo $NAME

# Mit Klammern (empfohlen)
echo ${NAME}

# In Strings
echo "Hallo $NAME"
echo "Hallo ${NAME}!"

# Ohne Interpolation (single quotes)
echo 'Hallo $NAME'  # Ausgabe: Hallo $NAME

Spezielle Variablen

$0      # Name des Scripts
$1-$9   # Erste 9 Parameter
${10}   # Ab 10. Parameter mit Klammern
$#      # Anzahl Parameter
$@      # Alle Parameter als separate Wörter
$*      # Alle Parameter als ein String
$?      # Exit-Code des letzten Befehls
$$      # PID des Scripts
$!      # PID des letzten Background-Prozesses
$_      # Letztes Argument des vorherigen Befehls

Beispiel:

#!/bin/bash

echo "Script-Name: $0"
echo "Erster Parameter: $1"
echo "Zweiter Parameter: $2"
echo "Anzahl Parameter: $#"
echo "Alle Parameter: $@"
echo "Script-PID: $$"

Aufruf:

./script.sh hallo welt
# Ausgabe:
# Script-Name: ./script.sh
# Erster Parameter: hallo
# Zweiter Parameter: welt
# Anzahl Parameter: 2
# Alle Parameter: hallo welt
# Script-PID: 12345

Umgebungsvariablen

# Wichtige System-Variablen
echo $HOME      # Home-Verzeichnis
echo $USER      # Aktueller User
echo $PATH      # Executable-Pfade
echo $PWD       # Aktuelles Verzeichnis
echo $OLDPWD    # Vorheriges Verzeichnis
echo $SHELL     # Aktuelle Shell
echo $HOSTNAME  # Hostname

# Eigene exportieren (für Child-Prozesse verfügbar)
export MEINE_VAR="Wert"

# Nur im aktuellen Script
LOKALE_VAR="Wert"

Command Substitution

Befehlsausgabe in Variable speichern:

# Moderne Syntax (empfohlen)
DATUM=$(date)
DATEIEN=$(ls -la)
ZEILEN=$(wc -l < datei.txt)

# Alte Syntax (veraltet)
DATUM=`date`

# Beispiel
ANZAHL=$(ls | wc -l)
echo "Es gibt $ANZAHL Dateien"

Variable mit Default-Wert

# Wenn Variable leer/unset, Default verwenden
echo ${VAR:-"Standard"}

# Wenn Variable leer/unset, Default setzen UND verwenden
echo ${VAR:="Standard"}

# Wenn Variable leer/unset, Fehler ausgeben
echo ${VAR:?"Variable VAR ist nicht gesetzt!"}

# Wenn Variable gesetzt, alternativen Wert verwenden
echo ${VAR:+"Alternativer Wert"}

Beispiel:

#!/bin/bash

# Port von Parameter oder Default 8080
PORT=${1:-8080}
echo "Verwende Port: $PORT"

# Aufruf ohne Parameter:
./script.sh
# Ausgabe: Verwende Port: 8080

# Aufruf mit Parameter:
./script.sh 3000
# Ausgabe: Verwende Port: 3000

Readonly und unset

# Variable als read-only deklarieren
readonly PI=3.14159
PI=3.14  # Fehler!

# Variable löschen
VAR="Test"
unset VAR
echo $VAR  # Leer

4. Benutzereingaben

read - Einfache Eingabe

#!/bin/bash

echo "Wie heißt du?"
read NAME
echo "Hallo $NAME!"

read mit Prompt

#!/bin/bash

read -p "Gib deinen Namen ein: " NAME
echo "Hallo $NAME!"

read ohne Echo (Passwort)

#!/bin/bash

read -sp "Passwort: " PASSWORD
echo ""  # Neue Zeile
echo "Passwort gesetzt: ${#PASSWORD} Zeichen"

read mit Timeout

#!/bin/bash

if read -t 5 -p "Eingabe (5 Sekunden): " INPUT; then
    echo "Du hast eingegeben: $INPUT"
else
    echo "Timeout!"
fi

read mit Default-Wert

#!/bin/bash

read -p "Port [8080]: " PORT
PORT=${PORT:-8080}
echo "Verwende Port: $PORT"

Mehrere Variablen gleichzeitig

#!/bin/bash

read -p "Vorname Nachname: " VORNAME NACHNAME
echo "Vorname: $VORNAME"
echo "Nachname: $NACHNAME"

read in Array

#!/bin/bash

echo "Gib mehrere Werte ein (Leerzeichen-getrennt):"
read -a ARRAY
echo "Erstes Element: ${ARRAY[0]}"
echo "Alle Elemente: ${ARRAY[@]}"

Zeile für Zeile einlesen

#!/bin/bash

while read -r ZEILE; do
    echo "Gelesen: $ZEILE"
done < datei.txt

Yes/No Abfrage

#!/bin/bash

read -p "Fortfahren? (j/n): " ANTWORT

if [[ "$ANTWORT" =~ ^[Jj]$ ]]; then
    echo "Weiter geht's!"
else
    echo "Abgebrochen"
    exit 1
fi

5. Bedingungen (if/else)

Grundstruktur

if [ BEDINGUNG ]; then
    # Code wenn wahr
fi
if [ BEDINGUNG ]; then
    # Code wenn wahr
else
    # Code wenn falsch
fi
if [ BEDINGUNG1 ]; then
    # Code wenn BEDINGUNG1 wahr
elif [ BEDINGUNG2 ]; then
    # Code wenn BEDINGUNG2 wahr
else
    # Code wenn alle falsch
fi

[ ] vs [[ ]]

Verwende [[ ]] (empfohlen in Bash):

# Moderne Bash-Syntax
if [[ $VAR == "test" ]]; then
    echo "Gleich"
fi

# Vorteile:
# - Pattern Matching
# - Keine Quoting-Probleme
# - && und || direkt nutzbar

Verwende [ ] (POSIX-kompatibel):

# Alte/portable Syntax
if [ "$VAR" = "test" ]; then
    echo "Gleich"
fi

# Nachteile:
# - Variablen müssen gequoted werden
# - Nur -a und -o für AND/OR

String-Vergleiche

# Gleichheit
if [[ "$STR1" == "$STR2" ]]; then
    echo "Gleich"
fi

# Ungleichheit
if [[ "$STR1" != "$STR2" ]]; then
    echo "Nicht gleich"
fi

# Leer?
if [[ -z "$STR" ]]; then
    echo "String ist leer"
fi

# Nicht leer?
if [[ -n "$STR" ]]; then
    echo "String ist nicht leer"
fi

# Pattern Matching (nur mit [[ ]])
if [[ "$DATEI" == *.txt ]]; then
    echo "Ist eine TXT-Datei"
fi

# Regex (nur mit [[ ]])
if [[ "$EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "Gültige E-Mail"
fi

Zahlen-Vergleiche

# Gleich
if [[ $NUM1 -eq $NUM2 ]]; then
    echo "Gleich"
fi

# Ungleich
if [[ $NUM1 -ne $NUM2 ]]; then
    echo "Ungleich"
fi

# Größer
if [[ $NUM1 -gt $NUM2 ]]; then
    echo "Größer"
fi

# Größer oder gleich
if [[ $NUM1 -ge $NUM2 ]]; then
    echo "Größer oder gleich"
fi

# Kleiner
if [[ $NUM1 -lt $NUM2 ]]; then
    echo "Kleiner"
fi

# Kleiner oder gleich
if [[ $NUM1 -le $NUM2 ]]; then
    echo "Kleiner oder gleich"
fi

Übersicht Zahlen-Operatoren:

  • -eq : equal (gleich)
  • -ne : not equal (ungleich)
  • -gt : greater than (größer)
  • -ge : greater or equal (größer oder gleich)
  • -lt : less than (kleiner)
  • -le : less or equal (kleiner oder gleich)

Datei-Tests

# Datei existiert?
if [[ -e "$DATEI" ]]; then
    echo "Existiert"
fi

# Ist eine normale Datei?
if [[ -f "$DATEI" ]]; then
    echo "Ist eine Datei"
fi

# Ist ein Verzeichnis?
if [[ -d "$VERZEICHNIS" ]]; then
    echo "Ist ein Verzeichnis"
fi

# Ist lesbar?
if [[ -r "$DATEI" ]]; then
    echo "Ist lesbar"
fi

# Ist schreibbar?
if [[ -w "$DATEI" ]]; then
    echo "Ist schreibbar"
fi

# Ist ausführbar?
if [[ -x "$DATEI" ]]; then
    echo "Ist ausführbar"
fi

# Ist ein Symlink?
if [[ -L "$DATEI" ]]; then
    echo "Ist ein Symlink"
fi

# Datei ist nicht leer?
if [[ -s "$DATEI" ]]; then
    echo "Datei ist nicht leer"
fi

# Datei1 ist neuer als Datei2?
if [[ "$DATEI1" -nt "$DATEI2" ]]; then
    echo "Datei1 ist neuer"
fi

# Datei1 ist älter als Datei2?
if [[ "$DATEI1" -ot "$DATEI2" ]]; then
    echo "Datei1 ist älter"
fi

Logische Operatoren

# UND (beide müssen wahr sein)
if [[ $AGE -ge 18 ]] && [[ $LAND == "DE" ]]; then
    echo "Erwachsen und in Deutschland"
fi

# Oder innerhalb [[ ]]
if [[ $AGE -ge 18 && $LAND == "DE" ]]; then
    echo "Erwachsen und in Deutschland"
fi

# ODER (eines muss wahr sein)
if [[ $TAG == "Samstag" ]] || [[ $TAG == "Sonntag" ]]; then
    echo "Wochenende!"
fi

# Oder innerhalb [[ ]]
if [[ $TAG == "Samstag" || $TAG == "Sonntag" ]]; then
    echo "Wochenende!"
fi

# NICHT (Negation)
if [[ ! -f "$DATEI" ]]; then
    echo "Datei existiert nicht"
fi

Case-Statement

Alternative zu vielen if/elif für Pattern-Matching:

#!/bin/bash

read -p "Wähle eine Option (a/b/c): " OPTION

case $OPTION in
    a)
        echo "Option A gewählt"
        ;;
    b)
        echo "Option B gewählt"
        ;;
    c)
        echo "Option C gewählt"
        ;;
    *)
        echo "Ungültige Option"
        ;;
esac

Mit Patterns:

#!/bin/bash

DATEI="$1"

case $DATEI in
    *.txt)
        echo "Text-Datei"
        ;;
    *.jpg|*.png|*.gif)
        echo "Bild-Datei"
        ;;
    *.sh)
        echo "Shell-Script"
        ;;
    *)
        echo "Unbekannter Typ"
        ;;
esac

Mit Ranges:

#!/bin/bash

read -p "Gib eine Zahl (1-100): " ZAHL

case $ZAHL in
    [1-9]|[1-9][0-9])
        echo "Gültige Zahl: $ZAHL"
        ;;
    100)
        echo "Maximum erreicht!"
        ;;
    *)
        echo "Ungültig"
        ;;
esac

6. Schleifen (for/while/until)

for-Schleife - über Liste

#!/bin/bash

# Über Wörter iterieren
for NAME in Max Lisa Tom; do
    echo "Hallo $NAME"
done

# Über Zahlenbereich (Bash 4+)
for i in {1..10}; do
    echo "Zahl: $i"
done

# Mit Schrittweite
for i in {0..100..10}; do
    echo "Zahl: $i"
done

# Über Dateien
for DATEI in *.txt; do
    echo "Verarbeite: $DATEI"
done

# Über Verzeichnisse
for DIR in */; do
    echo "Verzeichnis: $DIR"
done

for-Schleife - C-Style

#!/bin/bash

for ((i=0; i<10; i++)); do
    echo "Iteration $i"
done

# Mit mehreren Variablen
for ((i=0, j=10; i<10; i++, j--)); do
    echo "i=$i, j=$j"
done

for-Schleife - über Array

#!/bin/bash

NAMEN=("Max" "Lisa" "Tom")

for NAME in "${NAMEN[@]}"; do
    echo "Hallo $NAME"
done

# Mit Index
for i in "${!NAMEN[@]}"; do
    echo "Index $i: ${NAMEN[$i]}"
done

for-Schleife - über Command-Output

#!/bin/bash

# Über Zeilen
for ZEILE in $(cat datei.txt); do
    echo "$ZEILE"
done

# Besser: while read verwenden (siehe unten)

# Über Prozesse
for PID in $(pgrep firefox); do
    echo "Firefox PID: $PID"
done

while-Schleife

#!/bin/bash

# Einfaches Beispiel
i=0
while [[ $i -lt 10 ]]; do
    echo "i = $i"
    ((i++))
done

# Endlosschleife
while true; do
    echo "Drücke Ctrl+C zum Beenden"
    sleep 1
done

# Bedingung prüfen
while [[ -f "/tmp/lock" ]]; do
    echo "Warte auf Lock-Datei..."
    sleep 5
done

while read - Datei zeilenweise lesen

#!/bin/bash

# Beste Methode zum Datei-Lesen
while read -r ZEILE; do
    echo "Gelesen: $ZEILE"
done < datei.txt

# Mit IFS (Input Field Separator) für CSV
while IFS=',' read -r NAME ALTER STADT; do
    echo "Name: $NAME, Alter: $ALTER, Stadt: $STADT"
done < personen.csv

# Aus Pipe
cat datei.txt | while read -r ZEILE; do
    echo "$ZEILE"
done

# ABER ACHTUNG: Variablen in Pipe-while sind nicht außerhalb verfügbar!
# Besser: Process Substitution verwenden
while read -r ZEILE; do
    ((COUNTER++))
done < <(cat datei.txt)
echo "Zeilen gesamt: $COUNTER"  # Funktioniert!

until-Schleife

Läuft bis Bedingung WAHR wird (Gegenteil von while):

#!/bin/bash

i=0
until [[ $i -ge 10 ]]; do
    echo "i = $i"
    ((i++))
done

# Warten bis Datei existiert
until [[ -f "/tmp/bereit" ]]; do
    echo "Warte auf Datei..."
    sleep 1
done
echo "Datei existiert!"

break und continue

#!/bin/bash

# break - verlässt Schleife komplett
for i in {1..10}; do
    if [[ $i -eq 5 ]]; then
        break
    fi
    echo $i
done
# Ausgabe: 1 2 3 4

# continue - überspringt Rest der Iteration
for i in {1..10}; do
    if [[ $i -eq 5 ]]; then
        continue
    fi
    echo $i
done
# Ausgabe: 1 2 3 4 6 7 8 9 10

Verschachtelte Schleifen

#!/bin/bash

# Multiplikationstabelle
for i in {1..10}; do
    for j in {1..10}; do
        printf "%4d" $((i * j))
    done
    echo ""
done

# break mit Ebenen
for i in {1..5}; do
    for j in {1..5}; do
        if [[ $j -eq 3 ]]; then
            break 2  # Bricht beide Schleifen ab
        fi
        echo "i=$i, j=$j"
    done
done

7. Funktionen

Funktion definieren

# Methode 1 (empfohlen)
function meine_funktion {
    echo "Hallo aus der Funktion"
}

# Methode 2 (POSIX-kompatibel)
meine_funktion() {
    echo "Hallo aus der Funktion"
}

# Aufrufen
meine_funktion

Funktionen mit Parametern

#!/bin/bash

gruss() {
    echo "Hallo $1!"
}

gruss "Max"     # Ausgabe: Hallo Max!
gruss "Lisa"    # Ausgabe: Hallo Lisa!

# Mehrere Parameter
addiere() {
    local SUMME=$(($1 + $2))
    echo $SUMME
}

ERGEBNIS=$(addiere 5 3)
echo "5 + 3 = $ERGEBNIS"

Parameter in Funktionen

#!/bin/bash

funktion() {
    echo "Funktion: $0"           # Script-Name (nicht Funktionsname!)
    echo "Erster Parameter: $1"
    echo "Zweiter Parameter: $2"
    echo "Anzahl Parameter: $#"
    echo "Alle Parameter: $@"
}

funktion eins zwei drei

Return-Werte

#!/bin/bash

# return gibt Exit-Code zurück (0-255)
ist_gerade() {
    if [[ $(($1 % 2)) -eq 0 ]]; then
        return 0  # Wahr
    else
        return 1  # Falsch
    fi
}

# Verwendung
if ist_gerade 4; then
    echo "4 ist gerade"
fi

# Für andere Werte: echo verwenden
quadrat() {
    echo $(($1 * $1))
}

ERGEBNIS=$(quadrat 5)
echo "5² = $ERGEBNIS"

Lokale Variablen

#!/bin/bash

GLOBAL="Global"

funktion() {
    local LOKAL="Lokal"
    GLOBAL="Geändert"
    
    echo "In Funktion:"
    echo "  GLOBAL=$GLOBAL"
    echo "  LOKAL=$LOKAL"
}

funktion

echo "Außerhalb:"
echo "  GLOBAL=$GLOBAL"    # Geändert
echo "  LOKAL=$LOKAL"      # Leer

Wichtig: Immer local für Funktions-Variablen verwenden!

Funktionen mit Fehlerbehandlung

#!/bin/bash

datei_lesen() {
    local DATEI="$1"
    
    if [[ ! -f "$DATEI" ]]; then
        echo "Fehler: Datei $DATEI existiert nicht" >&2
        return 1
    fi
    
    cat "$DATEI"
    return 0
}

# Verwendung
if datei_lesen "test.txt"; then
    echo "Erfolgreich gelesen"
else
    echo "Fehler beim Lesen"
fi

Rekursive Funktionen

#!/bin/bash

# Fakultät berechnen
fakultaet() {
    if [[ $1 -le 1 ]]; then
        echo 1
    else
        local TEMP=$(fakultaet $(($1 - 1)))
        echo $(($1 * TEMP))
    fi
}

ERGEBNIS=$(fakultaet 5)
echo "5! = $ERGEBNIS"  # 120

Funktionen exportieren (für Subshells)

#!/bin/bash

meine_funktion() {
    echo "Hallo aus Funktion"
}

# Funktion exportieren
export -f meine_funktion

# In Subshell verfügbar
bash -c 'meine_funktion'

8. Arrays

Array erstellen

# Methode 1: Direkte Zuweisung
NAMEN=("Max" "Lisa" "Tom")

# Methode 2: Index-weise
NAMEN[0]="Max"
NAMEN[1]="Lisa"
NAMEN[2]="Tom"

# Methode 3: Aus Command
DATEIEN=($(ls))

# Methode 4: Leeres Array
declare -a ARRAY

Array-Elemente zugreifen

NAMEN=("Max" "Lisa" "Tom")

# Einzelnes Element
echo ${NAMEN[0]}   # Max
echo ${NAMEN[1]}   # Lisa

# Alle Elemente
echo ${NAMEN[@]}   # Max Lisa Tom
echo ${NAMEN[*]}   # Max Lisa Tom

# Anzahl Elemente
echo ${#NAMEN[@]}  # 3

# Indizes
echo ${!NAMEN[@]}  # 0 1 2

Array-Elemente hinzufügen/löschen

NAMEN=("Max" "Lisa")

# Hinzufügen
NAMEN+=("Tom")
NAMEN[3]="Anna"

# Löschen
unset NAMEN[1]     # Lisa löschen
echo ${NAMEN[@]}   # Max Tom Anna

# Komplett löschen
unset NAMEN

Über Array iterieren

NAMEN=("Max" "Lisa" "Tom")

# Methode 1: Über Werte
for NAME in "${NAMEN[@]}"; do
    echo "Name: $NAME"
done

# Methode 2: Über Indizes
for i in "${!NAMEN[@]}"; do
    echo "Index $i: ${NAMEN[$i]}"
done

# Methode 3: C-Style
for ((i=0; i<${#NAMEN[@]}; i++)); do
    echo "Position $i: ${NAMEN[$i]}"
done

Array slicing

ZAHLEN=(0 1 2 3 4 5 6 7 8 9)

# Ab Index 2
echo ${ZAHLEN[@]:2}      # 2 3 4 5 6 7 8 9

# Ab Index 2, Länge 3
echo ${ZAHLEN[@]:2:3}    # 2 3 4

# Letztes Element
echo ${ZAHLEN[@]: -1}    # 9

# Letzte 3 Elemente
echo ${ZAHLEN[@]: -3}    # 7 8 9

Array sortieren

NAMEN=("Tom" "Anna" "Max" "Lisa")

# Sortieren
IFS=$'\n' SORTIERT=($(sort <<<"${NAMEN[*]}"))
unset IFS

echo ${SORTIERT[@]}  # Anna Lisa Max Tom

Assoziative Arrays (Hash-Maps)

Bash 4+ unterstützt assoziative Arrays (Key-Value):

#!/bin/bash

# Deklarieren
declare -A PERSON

# Werte setzen
PERSON[name]="Max"
PERSON[alter]=25
PERSON[stadt]="Berlin"

# Zugriff
echo ${PERSON[name]}    # Max
echo ${PERSON[alter]}   # 25

# Alle Keys
echo ${!PERSON[@]}      # name alter stadt

# Alle Values
echo ${PERSON[@]}       # Max 25 Berlin

# Iterieren
for KEY in "${!PERSON[@]}"; do
    echo "$KEY: ${PERSON[$KEY]}"
done

Mehrdimensionale Arrays (Workaround)

Bash hat keine echten mehrdimensionalen Arrays, aber:

#!/bin/bash

# Simulieren mit Delimiter
declare -A MATRIX

MATRIX[0,0]=1
MATRIX[0,1]=2
MATRIX[1,0]=3
MATRIX[1,1]=4

echo ${MATRIX[0,0]}  # 1
echo ${MATRIX[1,1]}  # 4

# Iterieren
for i in 0 1; do
    for j in 0 1; do
        echo "[$i,$j] = ${MATRIX[$i,$j]}"
    done
done

9. String-Manipulation

String-Länge

TEXT="Hallo Welt"
echo ${#TEXT}  # 10

Substring

TEXT="Hallo Welt"

# Ab Position 6
echo ${TEXT:6}      # Welt

# Ab Position 0, Länge 5
echo ${TEXT:0:5}    # Hallo

# Letzte 4 Zeichen
echo ${TEXT: -4}    # Welt

String-Ersetzung

TEXT="Hallo Welt Welt"

# Erste Occurrence ersetzen
echo ${TEXT/Welt/World}        # Hallo World Welt

# Alle ersetzen
echo ${TEXT//Welt/World}       # Hallo World World

# Am Anfang ersetzen
echo ${TEXT/#Hallo/Hi}         # Hi Welt Welt

# Am Ende ersetzen
echo ${TEXT/%Welt/World}       # Hallo Welt World

String trimmen

TEXT="  Hallo Welt  "

# Führende Leerzeichen
echo ${TEXT#"${TEXT%%[![:space:]]*}"}

# Trailing Leerzeichen
echo ${TEXT%"${TEXT##*[![:space:]]}"}

# Beide (Funktion)
trim() {
    local var="$*"
    var="${var#"${var%%[![:space:]]*}"}"
    var="${var%"${var##*[![:space:]]}}"
    echo "$var"
}

echo "$(trim "$TEXT")"  # Hallo Welt

Prefix/Suffix entfernen

DATEI="test.txt.backup"

# Kürzestes Suffix entfernen (nicht-greedy)
echo ${DATEI%.*}        # test.txt

# Längstes Suffix entfernen (greedy)
echo ${DATEI%%.*}       # test

# Kürzestes Prefix entfernen
echo ${DATEI#*.}        # txt.backup

# Längstes Prefix entfernen
echo ${DATEI##*.}       # backup

Praktisches Beispiel - Dateinamen zerlegen:

DATEI="/pfad/zu/datei.txt"

# Nur Dateiname
echo ${DATEI##*/}       # datei.txt

# Nur Pfad
echo ${DATEI%/*}        # /pfad/zu

# Nur Name ohne Endung
BASENAME=${DATEI##*/}
echo ${BASENAME%.*}     # datei

# Nur Endung
echo ${DATEI##*.}       # txt

Case-Konvertierung

TEXT="Hallo Welt"

# Alles Kleinbuchstaben (Bash 4+)
echo ${TEXT,,}          # hallo welt

# Alles Großbuchstaben (Bash 4+)
echo ${TEXT^^}          # HALLO WELT

# Erster Buchstabe groß
echo ${TEXT^}           # Hallo welt

# Erster Buchstabe jedes Wortes groß
echo ${TEXT^^[hw]}      # Hallo Welt

String-Vergleich (erweitert)

TEXT="Hallo Welt"

# Beginnt mit?
if [[ $TEXT == Hallo* ]]; then
    echo "Beginnt mit Hallo"
fi

# Endet mit?
if [[ $TEXT == *Welt ]]; then
    echo "Endet mit Welt"
fi

# Enthält?
if [[ $TEXT == *ll* ]]; then
    echo "Enthält 'll'"
fi

# Regex-Match
if [[ $TEXT =~ ^[A-Z] ]]; then
    echo "Beginnt mit Großbuchstaben"
fi

String-Konkatenation

# Direkt
STR1="Hallo"
STR2="Welt"
GESAMT="$STR1 $STR2"
echo $GESAMT  # Hallo Welt

# Anhängen
TEXT="Hallo"
TEXT+=" Welt"
echo $TEXT    # Hallo Welt

# In Schleife
ERGEBNIS=""
for WORT in Hallo Welt wie gehts; do
    ERGEBNIS+="$WORT "
done
echo $ERGEBNIS  # Hallo Welt wie gehts

10. Mathematische Operationen

Arithmetische Expansion

# Methode 1: $((...))
SUMME=$((5 + 3))
echo $SUMME  # 8

PRODUKT=$((5 * 3))
echo $PRODUKT  # 15

# Methode 2: let
let SUMME=5+3
echo $SUMME  # 8

# Methode 3: ((...))
((i = 5 + 3))
echo $i  # 8

# Methode 4: expr (veraltet)
SUMME=$(expr 5 + 3)
echo $SUMME  # 8

Grundrechenarten

A=10
B=3

# Addition
echo $((A + B))   # 13

# Subtraktion
echo $((A - B))   # 7

# Multiplikation
echo $((A * B))   # 30

# Division (ganzzahlig!)
echo $((A / B))   # 3

# Modulo (Rest)
echo $((A % B))   # 1

# Potenz
echo $((A ** 2))  # 100

Inkrement/Dekrement

i=5

# Pre-Inkrement
echo $((++i))  # 6, i ist jetzt 6

# Post-Inkrement
echo $((i++))  # 6, i ist jetzt 7
echo $i        # 7

# Pre-Dekrement
echo $((--i))  # 6, i ist jetzt 6

# Post-Dekrement
echo $((i--))  # 6, i ist jetzt 5
echo $i        # 5

# Mit Zuweisung
((i += 5))     # i = i + 5
((i -= 2))     # i = i - 2
((i *= 3))     # i = i * 3
((i /= 2))     # i = i / 2

Gleitkomma-Arithmetik mit bc

# bc installieren falls nötig
# apt install bc

# Division mit Nachkommastellen
echo "scale=2; 10 / 3" | bc       # 3.33

# Komplexere Berechnung
echo "scale=4; sqrt(2)" | bc      # 1.4142

# Mit Variablen
A=10
B=3
ERGEBNIS=$(echo "scale=2; $A / $B" | bc)
echo $ERGEBNIS  # 3.33

# Funktion für Division
fdiv() {
    echo "scale=2; $1 / $2" | bc
}

echo $(fdiv 10 3)  # 3.33

Mathematische Funktionen mit bc

# Quadratwurzel
echo "sqrt(16)" | bc  # 4

# Sinus (in Radiant)
echo "s(1.5708)" | bc -l  # ~1 (sin(π/2))

# Cosinus
echo "c(0)" | bc -l       # 1

# Natürlicher Logarithmus
echo "l(2.71828)" | bc -l # ~1

# e-Funktion
echo "e(1)" | bc -l       # 2.71828

Vergleiche in Arithmetik

A=10
B=20

# In if-Bedingung
if ((A < B)); then
    echo "A ist kleiner"
fi

# Als Ausdruck
((A > B)) && echo "A größer" || echo "B größer"

# Mehrere Bedingungen
if ((A > 5 && A < 15)); then
    echo "A ist zwischen 5 und 15"
fi

Zufallszahlen

# Zufallszahl 0-32767
echo $RANDOM

# Zufallszahl in Bereich (0-99)
echo $((RANDOM % 100))

# Zufallszahl in Bereich (10-50)
echo $((RANDOM % 41 + 10))

# Funktion für Range
random_range() {
    local MIN=$1
    local MAX=$2
    echo $((RANDOM % (MAX - MIN + 1) + MIN))
}

echo $(random_range 1 100)

11. Dateien und Verzeichnisse

Datei lesen

# Komplette Datei
INHALT=$(cat datei.txt)

# Zeile für Zeile (empfohlen)
while read -r ZEILE; do
    echo "$ZEILE"
done < datei.txt

# Erste Zeile
ERSTE=$(head -n 1 datei.txt)

# Letzte Zeile
LETZTE=$(tail -n 1 datei.txt)

# Bestimmte Zeile (z.B. Zeile 5)
ZEILE=$(sed -n '5p' datei.txt)

Datei schreiben

# Überschreiben
echo "Hallo Welt" > datei.txt

# Anhängen
echo "Neue Zeile" >> datei.txt

# Mehrere Zeilen
cat > datei.txt << EOF
Zeile 1
Zeile 2
Zeile 3
EOF

# Mit Variablen
NAME="Max"
cat > config.txt << EOF
Name: $NAME
Datum: $(date)
EOF

# Aus Schleife
for i in {1..10}; do
    echo "Zeile $i" >> datei.txt
done

Datei existiert prüfen

DATEI="test.txt"

if [[ -f "$DATEI" ]]; then
    echo "Datei existiert"
else
    echo "Datei existiert nicht"
fi

# Erstellen falls nicht vorhanden
[[ -f "$DATEI" ]] || touch "$DATEI"

Verzeichnis erstellen

# Einfach
mkdir mein-ordner

# Mit Eltern-Verzeichnissen
mkdir -p /pfad/zum/tiefen/ordner

# Nur wenn nicht existiert
[[ -d "ordner" ]] || mkdir ordner

# In Script
BACKUP_DIR="/backup/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"

Dateien kopieren/verschieben

# Kopieren
cp quelle.txt ziel.txt

# Rekursiv kopieren
cp -r quell-ordner ziel-ordner

# Mit Backup
cp --backup=numbered datei.txt datei.txt

# Verschieben
mv alt.txt neu.txt

# In Verzeichnis verschieben
mv datei.txt /tmp/

# Mehrere Dateien
mv *.txt /backup/

Dateien löschen

# Datei löschen
rm datei.txt

# Mehrere Dateien
rm datei1.txt datei2.txt

# Mit Wildcard
rm *.tmp

# Verzeichnis löschen (leer)
rmdir ordner

# Verzeichnis löschen (mit Inhalt)
rm -r ordner

# Sicher löschen (mit Rückfrage)
rm -i datei.txt

# Force (keine Rückfrage)
rm -f datei.txt

Dateien finden

# Alle .txt Dateien im aktuellen Verzeichnis
find . -name "*.txt"

# Alle .txt Dateien rekursiv
find /pfad -name "*.txt"

# Case-insensitive
find . -iname "*.TXT"

# Nur Dateien (keine Verzeichnisse)
find . -type f -name "*.txt"

# Nur Verzeichnisse
find . -type d

# Geändert in letzten 7 Tagen
find . -mtime -7

# Größer als 10MB
find . -size +10M

# Mit Aktion
find . -name "*.tmp" -delete
find . -name "*.txt" -exec chmod 644 {} \;

Dateiattribute

DATEI="test.txt"

# Größe
GROESSE=$(stat -c %s "$DATEI")
echo "Größe: $GROESSE Bytes"

# Änderungszeit
MTIME=$(stat -c %Y "$DATEI")
echo "Geändert: $(date -d @$MTIME)"

# Berechtigungen
PERMS=$(stat -c %a "$DATEI")
echo "Rechte: $PERMS"

# Owner
OWNER=$(stat -c %U "$DATEI")
echo "Besitzer: $OWNER"

Temporäre Dateien

# Temporäre Datei erstellen
TEMP=$(mktemp)
echo "Temp-Datei: $TEMP"

# Temporäres Verzeichnis
TEMPDIR=$(mktemp -d)
echo "Temp-Verzeichnis: $TEMPDIR"

# Am Ende aufräumen
trap "rm -f $TEMP" EXIT

# Oder manuell
rm -f "$TEMP"

Arbeitsverzeichnis

# Aktuelles Verzeichnis
PWD=$(pwd)
echo "Aktuell: $PWD"

# Wechseln
cd /tmp

# Zurück zum vorherigen
cd -

# Zum Home
cd ~
# oder
cd

# In Script: Immer ins Script-Verzeichnis wechseln
cd "$(dirname "$0")"

12. Input/Output und Redirection

Standard-Streams

# 0 = stdin  (Standard-Input)
# 1 = stdout (Standard-Output)
# 2 = stderr (Standard-Error)

Output umleiten

# stdout in Datei (überschreiben)
echo "Hallo" > datei.txt

# stdout anhängen
echo "Welt" >> datei.txt

# stderr in Datei
befehl 2> fehler.log

# stdout UND stderr in Datei
befehl > ausgabe.log 2>&1
# oder (Bash 4+)
befehl &> ausgabe.log

# stdout und stderr getrennt
befehl > ausgabe.log 2> fehler.log

# stderr nach stdout umleiten
befehl 2>&1 | less

Input umleiten

# Aus Datei lesen
sort < unsortiert.txt

# Here Document
cat << EOF
Zeile 1
Zeile 2
EOF

# Here String
grep "test" <<< "test string"

# In Variable
VAR=$(cat << EOF
Mehrzeilig
Text
EOF
)

Pipes

# Output als Input
ls -la | grep ".txt"

# Mehrere Pipes
cat datei.txt | grep "fehler" | wc -l

# Mit tee (Output duplizieren)
echo "Hallo" | tee datei.txt  # Zeigt UND schreibt

# tee anhängen
echo "Welt" | tee -a datei.txt

/dev/null (Verwerfen)

# Output verwerfen
befehl > /dev/null

# Errors verwerfen
befehl 2> /dev/null

# Alles verwerfen
befehl &> /dev/null

# Nur Errors behalten
befehl 2>&1 > /dev/null

Process Substitution

# Output eines Befehls als Datei-Input
diff <(ls /tmp) <(ls /var/tmp)

# Mehrere Inputs
cat <(echo "Datei 1") <(echo "Datei 2")

# In while-Schleife (behält Variablen)
COUNTER=0
while read -r ZEILE; do
    ((COUNTER++))
done < <(cat datei.txt)
echo "Zeilen: $COUNTER"  # Funktioniert!

Named Pipes (FIFO)

# FIFO erstellen
mkfifo meine-pipe

# Terminal 1: Schreiben
echo "Hallo" > meine-pipe

# Terminal 2: Lesen
cat < meine-pipe

# Aufräumen
rm meine-pipe

Exec für Redirection

#!/bin/bash

# File Descriptor öffnen
exec 3< input.txt   # FD 3 zum Lesen
exec 4> output.txt  # FD 4 zum Schreiben

# Verwenden
while read -r ZEILE <&3; do
    echo "Gelesen: $ZEILE" >&4
done

# Schließen
exec 3<&-
exec 4>&-

13. Error Handling

Exit-Codes

# Eigene Exit-Codes setzen
exit 0   # Erfolg
exit 1   # Allgemeiner Fehler
exit 2   # Missbrauch von Shell-Befehl
exit 126 # Befehl nicht ausführbar
exit 127 # Befehl nicht gefunden
exit 130 # Script mit Ctrl+C beendet

# Exit-Code des letzten Befehls prüfen
ls /tmp
if [[ $? -eq 0 ]]; then
    echo "Erfolgreich"
fi

set -Optionen

#!/bin/bash

# Exit bei Fehler
set -e
# oder
set -o errexit

# Exit bei undefined Variable
set -u
# oder
set -o nounset

# Pipe-Fehler erkennen
set -o pipefail

# Kombiniert (empfohlen!)
set -euo pipefail

# Debug-Modus
set -x
# oder
set -o xtrace

Beispiel:

#!/bin/bash
set -euo pipefail

# Script stoppt hier wenn Fehler
cd /nicht/existent  # Fehler!
echo "Das wird nie ausgeführt"

trap - Cleanup bei Exit

#!/bin/bash

# Funktion für Cleanup
cleanup() {
    echo "Räume auf..."
    rm -f /tmp/lock
}

# Bei Exit aufrufen
trap cleanup EXIT

# Script läuft
echo "Script läuft..."
touch /tmp/lock
sleep 10

# cleanup() wird automatisch ausgeführt
# - bei normalem Exit
# - bei Ctrl+C
# - bei Fehler (mit set -e)

trap - Signale abfangen

#!/bin/bash

# Funktion für Interrupt
handle_interrupt() {
    echo ""
    echo "Unterbrochen! Räume auf..."
    exit 130
}

# Ctrl+C (SIGINT) abfangen
trap handle_interrupt INT

# Endlosschleife
while true; do
    echo "Läuft... (Ctrl+C zum Beenden)"
    sleep 1
done

Wichtige Signale:

  • INT - Ctrl+C
  • TERM - Termination signal
  • EXIT - Script-Ende (jeder Exit)
  • ERR - Bei Fehler (mit set -E)
  • DEBUG - Vor jedem Befehl

Fehler mit if abfangen

#!/bin/bash

if cp quelle.txt ziel.txt; then
    echo "Kopiert"
else
    echo "Fehler beim Kopieren" >&2
    exit 1
fi

# Kurzform mit ||
cp quelle.txt ziel.txt || {
    echo "Fehler!" >&2
    exit 1
}

Fehler mit || und &&

# Bei Erfolg weitermachen
befehl1 && befehl2

# Bei Fehler Alternativ-Befehl
befehl1 || echo "Fehlgeschlagen"

# Kombiniert
befehl1 && echo "Erfolg" || echo "Fehler"

# In Funktion
datei_kopieren() {
    cp "$1" "$2" && echo "OK" || return 1
}

Fehler-Nachrichten

#!/bin/bash

# Funktion für Fehler
error() {
    echo "FEHLER: $*" >&2
    exit 1
}

# Funktion für Warnung
warning() {
    echo "WARNUNG: $*" >&2
}

# Verwendung
[[ -f "wichtig.txt" ]] || error "Datei wichtig.txt fehlt!"
[[ -d "/tmp" ]] || warning "Kein /tmp Verzeichnis"

Assert-Funktion

#!/bin/bash

assert() {
    if ! "$@"; then
        echo "Assertion failed: $*" >&2
        exit 1
    fi
}

# Verwendung
assert [[ -f "config.txt" ]]
assert [[ $PORT -gt 0 ]]

Try-Catch simulieren

#!/bin/bash

try() {
    [[ $- = *e* ]]; SAVED_OPT_E=$?
    set +e
}

catch() {
    export exception_code=$?
    (( SAVED_OPT_E )) && set +e
    return $exception_code
}

# Verwendung
try
(
    # Code der fehlschlagen könnte
    cd /nicht/existent
    echo "Nicht erreicht"
)
catch || {
    case $exception_code in
        1)
            echo "Fehler 1: Verzeichnis nicht gefunden"
            ;;
        *)
            echo "Unbekannter Fehler: $exception_code"
            ;;
    esac
}

14. Reguläre Ausdrücke

Regex in [[ ]]

#!/bin/bash

TEXT="Hallo123"

# Einfaches Match
if [[ $TEXT =~ [0-9] ]]; then
    echo "Enthält Zahlen"
fi

# Komplexeres Pattern
if [[ $TEXT =~ ^[A-Z][a-z]+[0-9]+$ ]]; then
    echo "Passt: Großbuchstabe, Kleinbuchstaben, Zahlen"
fi

# Capture Groups
EMAIL="max@example.com"
if [[ $EMAIL =~ ^([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,})$ ]]; then
    echo "User: ${BASH_REMATCH[1]}"
    echo "Domain: ${BASH_REMATCH[2]}"
    echo "TLD: ${BASH_REMATCH[3]}"
fi

grep mit Regex

# Einfaches grep
grep "muster" datei.txt

# Case-insensitive
grep -i "muster" datei.txt

# Ganze Wörter
grep -w "wort" datei.txt

# Zeilen die NICHT matchen
grep -v "muster" datei.txt

# Extended Regex
grep -E "muster1|muster2" datei.txt

# Perl-compatible Regex
grep -P "\d{3}-\d{4}" datei.txt

# Mit Zeilennummern
grep -n "muster" datei.txt

# Nur Dateinamen
grep -l "muster" *.txt

# Rekursiv
grep -r "muster" /pfad/

sed - Stream Editor

# Ersetzen (erste Occurrence)
sed 's/alt/neu/' datei.txt

# Ersetzen (alle)
sed 's/alt/neu/g' datei.txt

# Ersetzen (case-insensitive)
sed 's/alt/neu/gi' datei.txt

# In-place editieren
sed -i 's/alt/neu/g' datei.txt

# Mit Backup
sed -i.bak 's/alt/neu/g' datei.txt

# Zeilen löschen
sed '/muster/d' datei.txt

# Bestimmte Zeile
sed '5d' datei.txt  # Zeile 5 löschen
sed -n '5p' datei.txt  # Nur Zeile 5 anzeigen

# Bereich
sed -n '10,20p' datei.txt  # Zeilen 10-20

awk - Text-Verarbeitung

# Erste Spalte
awk '{print $1}' datei.txt

# Mehrere Spalten
awk '{print $1, $3}' datei.txt

# Mit Bedingung
awk '$3 > 100 {print $1}' datei.txt

# Custom Delimiter
awk -F: '{print $1}' /etc/passwd

# Summe berechnen
awk '{sum += $1} END {print sum}' zahlen.txt

# Mit Regex
awk '/ERROR/ {print $0}' logfile.txt

Regex-Pattern Beispiele

# E-Mail
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}

# URL
https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?

# IP-Adresse (einfach)
[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}

# IP-Adresse (korrekt)
((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)

# Datum (YYYY-MM-DD)
[0-9]{4}-[0-9]{2}-[0-9]{2}

# Zeit (HH:MM:SS)
[0-9]{2}:[0-9]{2}:[0-9]{2}

# Telefonnummer (DE)
(\+49|0)[1-9][0-9]{1,14}

# Hexadezimal
#?[0-9a-fA-F]{6}

# UUID
[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}

15. Netzwerk und Ports

Port prüfen

#!/bin/bash

# Methode 1: Mit netstat
check_port_netstat() {
    netstat -tuln | grep -q ":$1 "
}

# Methode 2: Mit ss (moderner)
check_port_ss() {
    ss -tuln | grep -q ":$1 "
}

# Methode 3: Mit lsof
check_port_lsof() {
    lsof -i ":$1" > /dev/null 2>&1
}

# Methode 4: Mit /dev/tcp (nur Bash)
check_port_tcp() {
    timeout 1 bash -c "echo >/dev/tcp/localhost/$1" 2>/dev/null
}

# Verwenden
if check_port_ss 80; then
    echo "Port 80 ist belegt"
else
    echo "Port 80 ist frei"
fi

Nächsten freien Port finden

#!/bin/bash

find_free_port() {
    local PORT=${1:-60000}  # Startport
    
    while ss -tuln | grep -q ":$PORT "; do
        ((PORT++))
        # Sicherheit: Max Port
        if [[ $PORT -gt 65535 ]]; then
            echo "Kein freier Port gefunden!" >&2
            return 1
        fi
    done
    
    echo $PORT
}

# Verwenden
FREIER_PORT=$(find_free_port 60000)
echo "Nächster freier Port: $FREIER_PORT"

Optimierte Version (aus deinem Beispiel)

#!/bin/bash

# Startport
PORT=60000

# Funktion zum Überprüfen, ob ein Port frei ist
is_port_free() {
    ! ss -tla | awk '{print $4}' | grep -q ":$1$"
}

# Schleife, um den nächsten freien Port zu finden
while ! is_port_free $PORT; do
    ((PORT++))
    
    # Sicherheit
    if [[ $PORT -gt 65535 ]]; then
        echo "Fehler: Kein freier Port bis 65535!" >&2
        exit 1
    fi
done

echo "Next free port: $PORT"

HTTP-Request senden

#!/bin/bash

# Mit curl
RESPONSE=$(curl -s https://api.example.com/data)
echo $RESPONSE

# Mit Status-Code
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://example.com)
if [[ $HTTP_CODE -eq 200 ]]; then
    echo "OK"
fi

# Mit wget
wget -q -O - https://example.com

# POST-Request
curl -X POST -d "param=value" https://api.example.com

Ping-Check

#!/bin/bash

ping_host() {
    ping -c 1 -W 1 "$1" > /dev/null 2>&1
}

if ping_host "8.8.8.8"; then
    echo "Host erreichbar"
else
    echo "Host nicht erreichbar"
fi

DNS-Lookup

#!/bin/bash

# Mit host
IP=$(host google.com | awk '/has address/ {print $4}' | head -n1)
echo "Google IP: $IP"

# Mit dig
IP=$(dig +short google.com | head -n1)
echo "Google IP: $IP"

# Mit nslookup
nslookup google.com

Download mit Fortschritt

#!/bin/bash

# Mit curl
curl -O https://example.com/file.zip

# Mit wget
wget https://example.com/file.zip

# Mit Fortschrittsbalken
wget --progress=bar:force https://example.com/file.zip

# Zu spezifischem Pfad
curl -o /tmp/download.zip https://example.com/file.zip

16. Prozesse und Jobs

Prozess-Informationen

# Alle laufenden Prozesse
ps aux

# Eigene Prozesse
ps ux

# Nach Namen filtern
ps aux | grep firefox

# Mit pgrep
pgrep firefox

# PID von Prozess
pidof firefox

# Prozess-Baum
pstree
pstree -p  # Mit PIDs

Prozesse starten

# Normal (Vordergrund)
./script.sh

# Im Hintergrund
./script.sh &

# PID merken
./script.sh &
PID=$!
echo "Started with PID: $PID"

# Detached (überlebt Shell-Exit)
nohup ./script.sh &

# Mit Screen
screen -dmS myscript ./script.sh

# Mit Tmux
tmux new-session -d -s myscript ./script.sh

Prozesse kontrollieren

# Stoppen (SIGTERM)
kill $PID

# Force Kill (SIGKILL)
kill -9 $PID

# Alle Prozesse mit Namen
pkill firefox
killall firefox

# Mit Signal
pkill -TERM firefox
pkill -HUP nginx

# Prozess pausieren
kill -STOP $PID

# Prozess fortsetzen
kill -CONT $PID

Jobs verwalten

# Prozess im Hintergrund starten
./script.sh &

# Laufende Jobs anzeigen
jobs

# Job in Vordergrund holen
fg %1

# Job in Hintergrund schicken
bg %1

# Aktuellen Prozess in Hintergrund
# Ctrl+Z (pausiert)
# bg (fortsetzten im Hintergrund)

# Job beenden
kill %1

wait - Auf Prozesse warten

#!/bin/bash

# Prozesse starten
./script1.sh &
PID1=$!

./script2.sh &
PID2=$!

# Auf beide warten
wait $PID1
wait $PID2

echo "Beide fertig"

# Oder auf alle warten
./script1.sh &
./script2.sh &
wait
echo "Alle fertig"

Parallel ausführen

#!/bin/bash

# Einfach
for i in {1..10}; do
    ./process.sh $i &
done
wait

# Mit Limit (max 4 parallel)
parallel_limit() {
    local MAX=4
    local COUNT=0
    
    for i in {1..20}; do
        ./process.sh $i &
        ((COUNT++))
        
        if [[ $COUNT -ge $MAX ]]; then
            wait -n  # Warte auf einen
            ((COUNT--))
        fi
    done
    wait
}

Prozess-Output in Variable

# Synchron (wartet)
OUTPUT=$(./script.sh)

# Mit Fehlerbehandlung
if OUTPUT=$(./script.sh 2>&1); then
    echo "Erfolg: $OUTPUT"
else
    echo "Fehler: $OUTPUT"
fi

timeout - Prozess mit Zeitlimit

# Max 10 Sekunden
timeout 10 ./script.sh

# Mit Signal (SIGKILL nach 5s)
timeout -k 5 10 ./script.sh

# In if-Bedingung
if timeout 30 ./script.sh; then
    echo "Erfolgreich"
else
    EXIT_CODE=$?
    if [[ $EXIT_CODE -eq 124 ]]; then
        echo "Timeout!"
    else
        echo "Fehler: $EXIT_CODE"
    fi
fi

17. Debugging

set -x (Trace Mode)

#!/bin/bash

# Debug für ganzes Script
set -x

echo "Befehl 1"
echo "Befehl 2"

# Debug ausschalten
set +x

echo "Dieser wird nicht getraced"

Ausgabe:

+ echo 'Befehl 1'
Befehl 1
+ echo 'Befehl 2'
Befehl 2
+ set +x
Dieser wird nicht getraced

Selektives Debugging

#!/bin/bash

debug() {
    [[ $DEBUG ]] && echo "DEBUG: $*" >&2
}

debug "Script gestartet"

# Aufruf mit Debug:
DEBUG=1 ./script.sh

PS4 für bessere Debug-Ausgabe

#!/bin/bash

# Zeige Datei:Zeile vor jedem Befehl
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'

set -x
echo "Test"
my_function() {
    echo "In Funktion"
}
my_function
set +x

Ausgabe:

+(script.sh:5): echo Test
Test
+(script.sh:9): my_function
+(script.sh:7): my_function(): echo 'In Funktion'
In Funktion

bashdb - Debugger

# bashdb installieren
apt install bashdb

# Script debuggen
bashdb ./script.sh

# Befehle in bashdb:
# s - step (nächste Zeile)
# n - next (überspringt Funktionen)
# c - continue (bis Breakpoint)
# l - list (Code anzeigen)
# p VAR - print (Variable anzeigen)
# q - quit

Logging-Funktion

#!/bin/bash

# Log-Level
LOG_LEVEL=${LOG_LEVEL:-INFO}

log() {
    local LEVEL=$1
    shift
    local MESSAGE="$*"
    local TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$TIMESTAMP] [$LEVEL] $MESSAGE" | tee -a script.log
}

log_debug() {
    [[ $LOG_LEVEL == "DEBUG" ]] && log DEBUG "$@"
}

log_info() {
    log INFO "$@"
}

log_error() {
    log ERROR "$@" >&2
}

# Verwendung
log_debug "Debug-Nachricht"
log_info "Script gestartet"
log_error "Fehler aufgetreten!"

Assertion-Debugging

#!/bin/bash

assert() {
    if ! "$@"; then
        echo "ASSERTION FAILED: $*" >&2
        echo "  File: ${BASH_SOURCE[1]}" >&2
        echo "  Line: ${BASH_LINENO[0]}" >&2
        echo "  Function: ${FUNCNAME[1]}" >&2
        exit 1
    fi
}

# Verwendung
FILE="test.txt"
assert [[ -f "$FILE" ]]
assert [[ $(wc -l < "$FILE") -gt 0 ]]

ShellCheck - Statische Analyse

# ShellCheck installieren
apt install shellcheck

# Script prüfen
shellcheck script.sh

# Mit Severity-Level
shellcheck -S error script.sh

# Bestimmte Checks ausschließen
shellcheck -e SC2034 script.sh

# In Script: Zeile ignorieren
# shellcheck disable=SC2034
UNUSED_VAR="test"

18. Best Practices

Script-Template

#!/usr/bin/env bash

#############################################
# Script: mein-script.sh
# Beschreibung: Was macht das Script
# Autor: Name
# Datum: 2024-01-01
# Version: 1.0
#############################################

# Strikte Fehlerbehandlung
set -euo pipefail

# Script-Verzeichnis ermitteln
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Variablen
readonly VERSION="1.0"
readonly SCRIPT_NAME=$(basename "$0")

# Farben für Output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color

# Logging-Funktionen
log_info() {
    echo -e "${GREEN}[INFO]${NC} $*"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $*" >&2
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $*" >&2
}

# Cleanup-Funktion
cleanup() {
    log_info "Cleanup..."
    # Temporäre Dateien löschen, etc.
}

# Cleanup bei Exit
trap cleanup EXIT

# Hilfe-Funktion
usage() {
    cat << EOF
Usage: $SCRIPT_NAME [OPTIONS] <parameter>

Beschreibung des Scripts.

OPTIONS:
    -h, --help      Zeigt diese Hilfe
    -v, --version   Zeigt Version
    -d, --debug     Debug-Modus

EXAMPLES:
    $SCRIPT_NAME --help
    $SCRIPT_NAME parameter
EOF
}

# Parameter-Parsing
main() {
    # Parameter prüfen
    if [[ $# -eq 0 ]]; then
        usage
        exit 1
    fi
    
    # Parameter parsen
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                usage
                exit 0
                ;;
            -v|--version)
                echo "$SCRIPT_NAME version $VERSION"
                exit 0
                ;;
            -d|--debug)
                set -x
                shift
                ;;
            *)
                # Hauptlogik hier
                log_info "Verarbeite: $1"
                shift
                ;;
        esac
    done
}

# Script ausführen
main "$@"

Variablen-Naming

# KONSTANTEN in UPPERCASE
readonly MAX_RETRIES=3
readonly CONFIG_FILE="/etc/app/config"

# Globale Variablen in UPPERCASE
COUNTER=0
TOTAL_SIZE=0

# Lokale Variablen in lowercase
local temp_file="/tmp/xyz"
local user_name="max"

# Funktions-Parameter auch lowercase
process_file() {
    local file_path="$1"
    local output_dir="$2"
}

Funktionen organisieren

#!/bin/bash

#######################
# Hilfsfunktionen
#######################

log_info() {
    echo "[INFO] $*"
}

log_error() {
    echo "[ERROR] $*" >&2
}

#######################
# Validierungsfunktionen
#######################

validate_file() {
    [[ -f "$1" ]] || { log_error "Datei nicht gefunden: $1"; return 1; }
}

validate_port() {
    [[ $1 =~ ^[0-9]+$ ]] && [[ $1 -gt 0 ]] && [[ $1 -lt 65536 ]]
}

#######################
# Hauptfunktionen
#######################

process_data() {
    local input="$1"
    # ...
}

#######################
# Main
#######################

main() {
    # ...
}

main "$@"

Sicherheits-Best-Practices

#!/bin/bash

# 1. Strikte Fehlerbehandlung
set -euo pipefail

# 2. Variablen immer quoten
FILE="test file.txt"
cat "$FILE"  # RICHTIG
# cat $FILE  # FALSCH (bricht bei Leerzeichen)

# 3. Arrays für Listen
FILES=("file1.txt" "file2.txt" "file 3.txt")
for FILE in "${FILES[@]}"; do  # RICHTIG
    echo "$FILE"
done

# 4. Temporäre Dateien sicher erstellen
TEMP=$(mktemp)
trap "rm -f $TEMP" EXIT

# 5. User-Input validieren
read -p "Port: " PORT
if ! [[ $PORT =~ ^[0-9]+$ ]]; then
    echo "Ungültige Eingabe!" >&2
    exit 1
fi

# 6. Berechtigungen prüfen
if [[ ! -r "$FILE" ]]; then
    echo "Datei nicht lesbar: $FILE" >&2
    exit 1
fi

# 7. Relative Pfade vermeiden
cd "$(dirname "$0")" || exit 1

# 8. Externe Commands validieren
if ! command -v docker &> /dev/null; then
    echo "Docker nicht installiert!" >&2
    exit 1
fi

# 9. Secrets nicht in Variablen speichern wenn möglich
# Besser: Aus Datei lesen
PASSWORD=$(cat /secure/password.txt)

# 10. sudo vermeiden wo möglich
# Statt sudo für alles, Capabilities nutzen

Performance-Tipps

# LANGSAM: Externe Befehle in Schleife
for i in {1..1000}; do
    echo $i | grep "5"
done

# SCHNELL: Bash-interne Features
for i in {1..1000}; do
    [[ $i == *5* ]] && echo $i
done

# LANGSAM: Mehrfach Datei lesen
grep "ERROR" logfile.txt
grep "WARNING" logfile.txt

# SCHNELL: Einmal lesen
grep -E "ERROR|WARNING" logfile.txt

# LANGSAM: Viele kleine Writes
for i in {1..1000}; do
    echo $i >> file.txt
done

# SCHNELL: Batch-Write
{
    for i in {1..1000}; do
        echo $i
    done
} >> file.txt

Portabilität

#!/usr/bin/env bash

# POSIX-kompatibel vs. Bash-spezifisch

# POSIX (läuft überall):
if [ "$VAR" = "test" ]; then
    echo "OK"
fi

# Bash (nur in Bash):
if [[ $VAR == "test" ]]; then
    echo "OK"
fi

# Prüfen ob in Bash
if [ -z "$BASH_VERSION" ]; then
    echo "Dieses Script braucht Bash!" >&2
    exit 1
fi

# Feature-Detection statt OS-Detection
if command -v apt-get &> /dev/null; then
    # Debian/Ubuntu
    apt-get install package
elif command -v yum &> /dev/null; then
    # RHEL/CentOS
    yum install package
fi

19. Praktische Beispiele

Beispiel 1: Backup-Script ⭐

#!/usr/bin/env bash

set -euo pipefail

# Konfiguration
readonly BACKUP_SOURCE="/home/user/important"
readonly BACKUP_DEST="/backup"
readonly MAX_BACKUPS=7
readonly LOG_FILE="/var/log/backup.log"

# Logging
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

# Cleanup alte Backups
cleanup_old_backups() {
    log "Cleanup alte Backups..."
    
    local COUNT=$(find "$BACKUP_DEST" -name "backup-*.tar.gz" | wc -l)
    
    if [[ $COUNT -gt $MAX_BACKUPS ]]; then
        find "$BACKUP_DEST" -name "backup-*.tar.gz" -type f -printf '%T+ %p\n' \
            | sort | head -n -$MAX_BACKUPS | awk '{print $2}' \
            | xargs rm -f
        log "$(($COUNT - $MAX_BACKUPS)) alte Backups gelöscht"
    fi
}

# Hauptfunktion
create_backup() {
    local TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    local BACKUP_FILE="$BACKUP_DEST/backup-$TIMESTAMP.tar.gz"
    
    log "Starte Backup..."
    
    # Backup erstellen
    if tar -czf "$BACKUP_FILE" -C "$(dirname "$BACKUP_SOURCE")" "$(basename "$BACKUP_SOURCE")"; then
        local SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
        log "Backup erfolgreich: $BACKUP_FILE ($SIZE)"
    else
        log "ERROR: Backup fehlgeschlagen!"
        return 1
    fi
    
    # Alte Backups aufräumen
    cleanup_old_backups
}

# Main
main() {
    # Verzeichnis prüfen
    [[ -d "$BACKUP_SOURCE" ]] || { log "ERROR: Quelle nicht gefunden"; exit 1; }
    [[ -d "$BACKUP_DEST" ]] || mkdir -p "$BACKUP_DEST"
    
    # Backup erstellen
    create_backup
    
    log "Backup-Prozess abgeschlossen"
}

main "$@"

Beispiel 2: Server Health-Check

#!/usr/bin/env bash

set -euo pipefail

# Konfiguration
readonly CPU_THRESHOLD=80
readonly MEM_THRESHOLD=90
readonly DISK_THRESHOLD=85

# Farben
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m'

# Status ausgeben
print_status() {
    local STATUS=$1
    local MESSAGE=$2
    
    case $STATUS in
        OK)
            echo -e "${GREEN}✓${NC} $MESSAGE"
            ;;
        WARN)
            echo -e "${YELLOW}⚠${NC} $MESSAGE"
            ;;
        CRITICAL)
            echo -e "${RED}✗${NC} $MESSAGE"
            ;;
    esac
}

# CPU-Last prüfen
check_cpu() {
    local CPU_LOAD=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
    local CPU_INT=${CPU_LOAD%.*}
    
    if [[ $CPU_INT -lt $CPU_THRESHOLD ]]; then
        print_status OK "CPU: ${CPU_LOAD}%"
    else
        print_status CRITICAL "CPU: ${CPU_LOAD}% (Schwellwert: ${CPU_THRESHOLD}%)"
    fi
}

# RAM-Nutzung prüfen
check_memory() {
    local MEM_USED=$(free | grep Mem | awk '{printf("%.0f", $3/$2 * 100)}')
    
    if [[ $MEM_USED -lt $MEM_THRESHOLD ]]; then
        print_status OK "RAM: ${MEM_USED}%"
    else
        print_status CRITICAL "RAM: ${MEM_USED}% (Schwellwert: ${MEM_THRESHOLD}%)"
    fi
}

# Disk-Space prüfen
check_disk() {
    while read -r LINE; do
        local USAGE=$(echo "$LINE" | awk '{print $5}' | sed 's/%//')
        local MOUNT=$(echo "$LINE" | awk '{print $6}')
        
        if [[ $USAGE -lt $DISK_THRESHOLD ]]; then
            print_status OK "Disk $MOUNT: ${USAGE}%"
        else
            print_status CRITICAL "Disk $MOUNT: ${USAGE}% (Schwellwert: ${DISK_THRESHOLD}%)"
        fi
    done < <(df -h | grep -vE '^Filesystem|tmpfs|cdrom')
}

# Services prüfen
check_services() {
    local SERVICES=("ssh" "nginx" "mysql")
    
    for SERVICE in "${SERVICES[@]}"; do
        if systemctl is-active --quiet "$SERVICE"; then
            print_status OK "Service $SERVICE läuft"
        else
            print_status CRITICAL "Service $SERVICE läuft NICHT"
        fi
    done
}

# Main
main() {
    echo "=== Server Health Check ==="
    echo "Zeitpunkt: $(date)"
    echo ""
    
    echo "--- CPU ---"
    check_cpu
    
    echo ""
    echo "--- Memory ---"
    check_memory
    
    echo ""
    echo "--- Disk Space ---"
    check_disk
    
    echo ""
    echo "--- Services ---"
    check_services
}

main "$@"

Beispiel 3: Log-Analyzer

#!/usr/bin/env bash

set -euo pipefail

# Konfiguration
readonly LOG_FILE="${1:-/var/log/syslog}"
readonly REPORT_FILE="log_report_$(date +%Y%m%d_%H%M%S).txt"

# Report generieren
generate_report() {
    {
        echo "=== Log Analysis Report ==="
        echo "File: $LOG_FILE"
        echo "Generated: $(date)"
        echo ""
        
        echo "--- Top 10 Error Messages ---"
        grep -i "error" "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
        echo ""
        
        echo "--- Top 10 Warning Messages ---"
        grep -i "warning" "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
        echo ""
        
        echo "--- Activity by Hour ---"
        awk '{print $3}' "$LOG_FILE" | cut -d: -f1 | sort | uniq -c
        echo ""
        
        echo "--- Failed SSH Logins ---"
        grep "Failed password" "$LOG_FILE" | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn
        echo ""
        
        echo "--- Disk Space Warnings ---"
        grep -i "disk.*full\|no space left" "$LOG_FILE"
        
    } > "$REPORT_FILE"
    
    echo "Report erstellt: $REPORT_FILE"
}

# Main
main() {
    if [[ ! -f "$LOG_FILE" ]]; then
        echo "Log-Datei nicht gefunden: $LOG_FILE" >&2
        exit 1
    fi
    
    generate_report
}

main "$@"

Beispiel 4: Batch Image Converter

#!/usr/bin/env bash

set -euo pipefail

# Konfiguration
readonly INPUT_DIR="${1:-.}"
readonly OUTPUT_DIR="${2:-./converted}"
readonly FORMAT="${3:-jpg}"
readonly QUALITY=85

# Farben
readonly GREEN='\033[0;32m'
readonly RED='\033[0;31m'
readonly NC='\033[0m'

# ImageMagick prüfen
check_dependencies() {
    if ! command -v convert &> /dev/null; then
        echo -e "${RED}Fehler: ImageMagick nicht installiert${NC}" >&2
        echo "Installieren mit: sudo apt install imagemagick" >&2
        exit 1
    fi
}

# Bilder konvertieren
convert_images() {
    local COUNT=0
    local SUCCESS=0
    local FAILED=0
    
    # Output-Verzeichnis erstellen
    mkdir -p "$OUTPUT_DIR"
    
    # Alle Bilder finden und konvertieren
    while IFS= read -r -d '' FILE; do
        ((COUNT++))
        
        local BASENAME=$(basename "$FILE")
        local FILENAME="${BASENAME%.*}"
        local OUTPUT="$OUTPUT_DIR/${FILENAME}.$FORMAT"
        
        echo -n "Konvertiere $BASENAME ... "
        
        if convert "$FILE" -quality $QUALITY "$OUTPUT" 2>/dev/null; then
            echo -e "${GREEN}OK${NC}"
            ((SUCCESS++))
        else
            echo -e "${RED}FEHLER${NC}"
            ((FAILED++))
        fi
    done < <(find "$INPUT_DIR" -maxdepth 1 -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" \) -print0)
    
    # Zusammenfassung
    echo ""
    echo "=== Zusammenfassung ==="
    echo "Gesamt: $COUNT"
    echo -e "Erfolgreich: ${GREEN}$SUCCESS${NC}"
    echo -e "Fehlgeschlagen: ${RED}$FAILED${NC}"
}

# Main
main() {
    echo "=== Batch Image Converter ==="
    echo "Input: $INPUT_DIR"
    echo "Output: $OUTPUT_DIR"
    echo "Format: $FORMAT"
    echo "Qualität: $QUALITY"
    echo ""
    
    check_dependencies
    convert_images
}

main "$@"

Beispiel 5: CSV-Processor

#!/usr/bin/env bash

set -euo pipefail

# CSV-Datei verarbeiten
process_csv() {
    local INPUT_FILE="$1"
    local TOTAL=0
    local COUNT=0
    
    # Header überspringen und Daten verarbeiten
    while IFS=',' read -r NAME AGE CITY; do
        # Header überspringen
        if [[ $NAME == "Name" ]]; then
            continue
        fi
        
        ((COUNT++))
        TOTAL=$((TOTAL + AGE))
        
        echo "Person #$COUNT: $NAME, $AGE Jahre, aus $CITY"
    done < "$INPUT_FILE"
    
    # Durchschnitt berechnen
    if [[ $COUNT -gt 0 ]]; then
        local AVG=$((TOTAL / COUNT))
        echo ""
        echo "Durchschnittsalter: $AVG Jahre"
    fi
}

# Main
main() {
    local CSV_FILE="${1:-persons.csv}"
    
    if [[ ! -f "$CSV_FILE" ]]; then
        echo "CSV-Datei nicht gefunden: $CSV_FILE" >&2
        exit 1
    fi
    
    process_csv "$CSV_FILE"
}

main "$@"

Beispiel 6: Interactive Menu

#!/usr/bin/env bash

set -euo pipefail

# Menu anzeigen
show_menu() {
    clear
    echo "========================"
    echo "    Hauptmenü"
    echo "========================"
    echo "1. System-Info anzeigen"
    echo "2. Backup erstellen"
    echo "3. Logs anzeigen"
    echo "4. Services verwalten"
    echo "5. Beenden"
    echo "========================"
}

# System-Info
show_system_info() {
    clear
    echo "=== System-Information ==="
    echo "Hostname: $(hostname)"
    echo "Kernel: $(uname -r)"
    echo "Uptime: $(uptime -p)"
    echo "CPU: $(lscpu | grep "Model name" | cut -d: -f2 | xargs)"
    echo "RAM: $(free -h | grep Mem | awk '{print $3 "/" $2}')"
    echo ""
    read -p "Drücke Enter zum Fortfahren..."
}

# Backup
create_backup_interactive() {
    clear
    echo "=== Backup erstellen ==="
    read -p "Quell-Verzeichnis: " SOURCE
    read -p "Ziel-Verzeichnis: " DEST
    
    if [[ -d "$SOURCE" ]]; then
        echo "Erstelle Backup..."
        tar -czf "$DEST/backup_$(date +%Y%m%d_%H%M%S).tar.gz" "$SOURCE"
        echo "Backup erfolgreich!"
    else
        echo "Fehler: Quelle nicht gefunden"
    fi
    
    read -p "Drücke Enter zum Fortfahren..."
}

# Logs anzeigen
show_logs() {
    clear
    echo "=== System-Logs ==="
    echo "1. Letzte 20 Zeilen"
    echo "2. Errors"
    echo "3. Zurück"
    read -p "Auswahl: " CHOICE
    
    case $CHOICE in
        1)
            journalctl -n 20
            ;;
        2)
            journalctl -p err -n 50
            ;;
        3)
            return
            ;;
    esac
    
    read -p "Drücke Enter zum Fortfahren..."
}

# Services
manage_services() {
    clear
    echo "=== Service-Verwaltung ==="
    echo "1. Status anzeigen"
    echo "2. Service starten"
    echo "3. Service stoppen"
    echo "4. Zurück"
    read -p "Auswahl: " CHOICE
    
    case $CHOICE in
        1)
            systemctl list-units --type=service --state=running
            ;;
        2)
            read -p "Service-Name: " SERVICE
            sudo systemctl start "$SERVICE"
            echo "Service gestartet"
            ;;
        3)
            read -p "Service-Name: " SERVICE
            sudo systemctl stop "$SERVICE"
            echo "Service gestoppt"
            ;;
        4)
            return
            ;;
    esac
    
    read -p "Drücke Enter zum Fortfahren..."
}

# Main Loop
main() {
    while true; do
        show_menu
        read -p "Wähle eine Option: " CHOICE
        
        case $CHOICE in
            1)
                show_system_info
                ;;
            2)
                create_backup_interactive
                ;;
            3)
                show_logs
                ;;
            4)
                manage_services
                ;;
            5)
                echo "Auf Wiedersehen!"
                exit 0
                ;;
            *)
                echo "Ungültige Auswahl"
                sleep 1
                ;;
        esac
    done
}

main "$@"

Ende der Bash Scripting Anleitung! 🎉

Diese Anleitung deckt alle wichtigen Aspekte des Bash-Scriptings ab. Für spezifische Anwendungsfälle können die Beispiele als Templates verwendet und angepasst werden.