Beherrsche den Linux-Dateibaum, lerne blitzschnelle Pfad-Navigation und automatisiere Dateioperationen mit Wildcards und Brace Expansion.
+
+
+
+
+
+
+
🧭 1. Der Unix-Dateibaum & Pfad-Navigation
+
In Unix gibt es keine Laufwerksbuchstaben (wie C: oder D:). Alles entspringt dem Root-Verzeichnis / (Single Root Tree). Wir unterscheiden strikt zwischen absoluten und relativen Pfaden:
+
+
# Wo bin ich?
+pwd # Ausgabe: /home/devops/projects
+
+# Relative vs. Absolute Navigation:
+cd /var/log/nginx # Absoluter Pfad: Beginnt immer mit '/'
+cd ../../etc # Relativer Pfad: Zwei Ebenen hoch, dann in 'etc'
+cd ~ # Wechselt ins Home-Verzeichnis (/home/user)
+cd - # Wechselt zum vorherigen Verzeichnis zurück
+
+# Detailliertes Listing mit Berechtigungen und Dateigrößen:
+ls -lah # -l: Long Format, -a: Versteckte Dateien, -h: Human-readable (KB/MB)
+
+
+
+
+
📁 2. Dateisystem-Operationen & Mächtiges Globbing
+
Als DevOps-Ingenieur erstellst, kopierst und reorganisierst du Konfigurationsdateien im Sekundentakt. Die Shell bietet hierfür mächtige Werkzeuge:
🏢 Didaktische Analogie: Das Logistikzentrum des Betriebssystems
+
Der Dateibaum: Stell dir das Dateisystem wie ein riesiges, strukturiertes Hochregallager vor. / ist der Haupteingang. mkdir -p baut dir sofort eine ganze Gasse mit Fachböden auf.
+
Globbing (* und {a,b}): Anstatt jedes Paket einzeln mit der Hand zu scannen, hältst du eine Schablone hoch: *.yaml greift automatisch alle blauen Kisten aus dem Regal, während {dev,prod} mit einem Handgriff zwei vorgefertigte Etiketten druckt!
+
+
+
+
+
+
📑 3. Cheat-Sheet: Navigation & Wildcards
+
+
+
+
+
Befehl / Muster
+
Beispiel
+
Bedeutung & DevOps-Einsatz
+
+
+
+
+
mkdir -p <pfad>
+
mkdir -p /srv/app/releases
+
Erstellt Elternverzeichnisse automatisch ohne Fehler
+
+
+
cp -r <src> <dst>
+
cp -r ./templates /etc/nginx/
+
Kopiert ganze Verzeichnisbäume rekursiv
+
+
+
mv <src> <dst>
+
mv old.env .env.backup
+
Verschiebt oder benennt Dateien atomar um
+
+
+
rm -rf <pfad>
+
rm -rf ./temp_workspace
+
Löscht rekursiv und erzwingt Löschen ohne Prompt
+
+
+
* (Wildcard)
+
rm -f *.log
+
Matcht 0 oder beliebig viele Zeichen
+
+
+
? (Single Char)
+
ls node_v?.log
+
Matcht exakt ein einziges Zeichen
+
+
+
{a,b,c}
+
touch {app,db}_{1..3}.conf
+
Brace Expansion zur raschen Permutationserstellung
+
+
+
+
+
+
+
+
+
🎯 Aufgaben & Checkliste in aufgabe.sh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💡 DevOps Pro-Tipp: Quoting in Shell-Skripten
+
Setze Variablen bei Pfadangaben in Bash-Skripten stets in doppelte Anführungszeichen: "$basis_dir/src/components". Das verhindert gefürchtete Word Splitting Fehler, falls ein Verzeichnisname einmal Leerzeichen enthält!
Die Rohre (|): Ein Rohr verbindet den Ausfluss des einen Beckens direkt mit dem Einlauf des nächsten Filters.
+
Das T-Stück (tee): Wie ein Installateur-T-Stück zweigt tee das fließende Wasser gleichzeitig in eine Vorratsflasche (Datei) ab, während der Hauptstrahl ungebremst weiter durch den Garten (stdout) fließt!
+
+
+
+
+
+
📑 3. Cheat-Sheet: Stream-Operatoren
+
+
+
+
+
Operator
+
Beispiel
+
Bedeutung & Verhalten
+
+
+
+
+
> datei
+
echo "test" > out.txt
+
Leitet stdout um und überschreibt die Datei
+
+
+
>> datei
+
echo "new line" >> out.txt
+
Leitet stdout um und hängt an
+
+
+
2> datei
+
cmd 2> error.log
+
Leitet nur stderr in die Datei um
+
+
+
2>&1
+
cmd > out.log 2>&1
+
Dupliziert stderr auf stdout
+
+
+
| (Pipe)
+
cat log | grep ERR
+
Verbindet stdout von Links mit stdin von Rechts
+
+
+
tee datei
+
cmd | tee -a run.log
+
Schreibt in Datei und leitet weiter auf stdout
+
+
+
<<EOF
+
cat <<EOF ... EOF
+
Here-Document für saubere mehrzeilige Textblöcke
+
+
+
+
+
+
+
+
+
🎯 Aufgaben & Checkliste in aufgabe.sh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💡 DevOps Pro-Tipp: Cronjob-Ausgaben bündeln
+
In Produktions-Cronjobs solltest du stets 0 2 * * * /opt/backup.sh >> /var/log/backup.log 2>&1 verwenden. So gehen dir weder Statusmeldungen noch kritische Fehlerursachen im Logfile verloren!
+
+
diff --git a/courses/bash/02_streams_pipes_und_redirection/musterloesung.sh b/courses/bash/02_streams_pipes_und_redirection/musterloesung.sh
new file mode 100644
index 0000000..b475b64
--- /dev/null
+++ b/courses/bash/02_streams_pipes_und_redirection/musterloesung.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# 🐧 BASH 02: MUSTERLÖSUNG
+# ==============================================================================
+
+erstelle_system_bericht() {
+ local ziel_datei="$1"
+ local server_name="$2"
+ local os_typ="$3"
+
+ cat < "$ziel_datei"
+=== SYSTEM BERICHT ===
+SERVER: $server_name
+OS: $os_typ
+STATUS: INITIALISIERT
+======================
+EOF
+}
+
+haenge_status_an() {
+ local ziel_datei="$1"
+ local status_meldung="$2"
+ echo "[STATUS] $status_meldung" >> "$ziel_datei"
+}
+
+trenne_befehls_ausgabe() {
+ local befehl="$1"
+ local out_datei="$2"
+ local err_datei="$3"
+ eval "$befehl" > "$out_datei" 2> "$err_datei"
+}
+
+unterdruecke_fehler() {
+ local befehl="$1"
+ eval "$befehl" 2> /dev/null
+}
+
+pipe_und_tee_pipeline() {
+ local eingabe_text="$1"
+ local tee_datei="$2"
+ printf "%s\n" "$eingabe_text" | grep -v '^#' | tr '[:lower:]' '[:upper:]' | tee "$tee_datei" | sort
+}
diff --git a/courses/bash/02_streams_pipes_und_redirection/test_aufgabe.sh b/courses/bash/02_streams_pipes_und_redirection/test_aufgabe.sh
new file mode 100644
index 0000000..8edc2b9
--- /dev/null
+++ b/courses/bash/02_streams_pipes_und_redirection/test_aufgabe.sh
@@ -0,0 +1,123 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# 🧪 BATS-INSPIRIERTE TESTSUITE: BASH 02 - STREAMS, PIPES & REDIRECTION
+# ==============================================================================
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+TARGET_SCRIPT="${1:-aufgabe.sh}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+TOTAL_TESTS=0
+PASSED_TESTS=0
+FAILED_TESTS=0
+
+echo -e "${BLUE}================================================================${NC}"
+echo -e "${BLUE}🧪 TESTSUITE: Bash 02 - Streams, Pipes & Redirection${NC}"
+echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
+echo -e "${BLUE}================================================================${NC}\n"
+
+if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
+ echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
+ exit 1
+fi
+
+source "$SCRIPT_DIR/$TARGET_SCRIPT"
+
+TEST_ROOT=$(mktemp -d /tmp/bash02_test_XXXXXX)
+trap 'rm -rf "$TEST_ROOT"' EXIT
+
+run_test() {
+ local test_name="$1"
+ local test_cmd="$2"
+ ((TOTAL_TESTS++))
+
+ echo -e "▶ Test ${TOTAL_TESTS}: ${test_name}"
+ if eval "$test_cmd"; then
+ echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
+ ((PASSED_TESTS++))
+ else
+ echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
+ ((FAILED_TESTS++))
+ fi
+}
+
+# TEST: Teilziel 1 - erstelle_system_bericht
+run_test "System-Bericht mit Here-Doc und Redirection schreiben" '
+ REPORT="$TEST_ROOT/report.txt"
+ erstelle_system_bericht "$REPORT" "prod-web-01" "Ubuntu 24.04"
+ [ -f "$REPORT" ] && \
+ grep -q "=== SYSTEM BERICHT ===" "$REPORT" && \
+ grep -q "SERVER: prod-web-01" "$REPORT" && \
+ grep -q "OS: Ubuntu 24.04" "$REPORT" && \
+ grep -q "STATUS: INITIALISIERT" "$REPORT"
+'
+
+# TEST: Teilziel 2 - haenge_status_an
+run_test "Statuszeile mit >> an bestehende Datei anhaengen" '
+ LOG="$TEST_ROOT/app.log"
+ echo "HEADER" > "$LOG"
+ haenge_status_an "$LOG" "Database connection established"
+ haenge_status_an "$LOG" "Listening on port 8080"
+ grep -q "\[STATUS\] Database connection established" "$LOG" && \
+ grep -q "\[STATUS\] Listening on port 8080" "$LOG" && \
+ [ "$(wc -l < "$LOG")" -eq 3 ]
+'
+
+# TEST: Teilziel 3 - trenne_befehls_ausgabe
+run_test "Standard Output (1>) und Error (2>) sauber trennen" '
+ OUT="$TEST_ROOT/out.log"
+ ERR="$TEST_ROOT/err.log"
+ CMD="{ echo \"Erfolgreicher Job\"; echo \"Fehlerhafter Zugriff\" >&2; }"
+ trenne_befehls_ausgabe "$CMD" "$OUT" "$ERR"
+ grep -q "Erfolgreicher Job" "$OUT" && \
+ grep -q "Fehlerhafter Zugriff" "$ERR" && \
+ ! grep -q "Fehlerhafter Zugriff" "$OUT" && \
+ ! grep -q "Erfolgreicher Job" "$ERR"
+'
+
+# TEST: Teilziel 4 - unterdruecke_fehler
+run_test "Fehlermeldungen nach /dev/null umleiten" '
+ CMD="{ echo \"Wichtige Ausgabe\"; echo \"Warnung 404\" >&2; }"
+ OUTPUT=$(unterdruecke_fehler "$CMD")
+ [ "$OUTPUT" = "Wichtige Ausgabe" ]
+'
+
+# TEST: Teilziel 5 - pipe_und_tee_pipeline
+run_test "Pipeline mit Filter, Uppercase, tee und sort" '
+ RAW=$'\
+# Ignore comment\n\
+nginx\n\
+docker\n\
+# Another comment\n\
+apache\n\
+redis'
+ TEE_FILE="$TEST_ROOT/tee_output.log"
+ FINAL_OUT=$(pipe_und_tee_pipeline "$RAW" "$TEE_FILE")
+
+ # Pruefe Tee-Datei (ungestrichen, uppercase)
+ [ -f "$TEE_FILE" ] && \
+ grep -q "NGINX" "$TEE_FILE" && \
+ grep -q "DOCKER" "$TEE_FILE" && \
+ ! grep -q "#" "$TEE_FILE" && \
+
+ # Pruefe Endausgabe (sortiert)
+ FIRST_LINE=$(echo "$FINAL_OUT" | head -n 1)
+ LAST_LINE=$(echo "$FINAL_OUT" | tail -n 1)
+ [ "$FIRST_LINE" = "APACHE" ] && [ "$LAST_LINE" = "REDIS" ]
+'
+
+echo -e "${BLUE}================================================================${NC}"
+if [ "$FAILED_TESTS" -eq 0 ]; then
+ echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in Bash 02 erfolgreich bestanden!${NC}"
+ echo -e "${BLUE}================================================================${NC}"
+ exit 0
+else
+ echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
+ echo -e "${BLUE}================================================================${NC}"
+ exit 1
+fi
diff --git a/courses/bash/03_variablen_environment_und_subshells/README.md b/courses/bash/03_variablen_environment_und_subshells/README.md
new file mode 100644
index 0000000..9c60b31
--- /dev/null
+++ b/courses/bash/03_variablen_environment_und_subshells/README.md
@@ -0,0 +1,55 @@
+# Bash 03: Variablen, Environment, Expansion & Arithmetik ⚡
+
+Willkommen zu **Modul 03** des Linux Bash & DevOps Lehrpfads!
+
+In diesem Modul meisterst du lokale vs. globale Umgebungsvariablen (`export`, `$PATH`), Subshells vs. Current-Shell, moderne Command Substitution (`$(...)`), mächtige native String-Manipulationen per Parameter Expansion und Ganzzahl-Arithmetik (`$(( ... ))`).
+
+---
+
+## 💡 1. Das Wichtigste in Kürze
+
+### Lokale Variablen vs. `export`
+- Lokale Variablen (`NAME="Wert"`) existieren nur in der aktuellen Shell-Instanz.
+- Mit `export NAME="Wert"` wird die Variable an alle Kind-Prozesse (Subshells, Skripte, CLI-Programme) vererbt.
+- `$PATH` ist die durch Doppelpunkte getrennte Liste von Verzeichnissen, in denen die Shell nach ausführbaren Programmen sucht (`export PATH="/opt/bin:$PATH"`).
+
+### Command Substitution `$(...)`
+Führt ein Shell-Kommando aus und ersetzt den Ausdruck durch dessen `stdout`:
+```bash
+AKTUELLER_USER=$(whoami)
+HEUTIGES_DATUM=$(date +%Y-%m-%d)
+BACKUP_NAME="backup_${AKTUELLER_USER}_${HEUTIGES_DATUM}.tar.gz"
+```
+
+### Native Parameter Expansion (Kein `sed`/`awk` nötig!)
+Bash kann Strings intern extrem schnell und speicherschonend manipulieren:
+- **Default-Wert**: `${VAR:-"standardwert"}` (nutzt Fallback, falls `$VAR` nicht gesetzt/leer)
+- **Präfix entfernen**: `${PFAD#*/}` (kürzester Treffer) bzw. `${PFAD##*/}` (längster Treffer -> Dateiname)
+- **Suffix entfernen**: `${PFAD%/*}` (Verzeichnis) bzw. `${DATEI%.*}` (Dateiname ohne Endung)
+- **String-Länge**: `${#VAR}`
+
+### Shell-Arithmetik `$(( ... ))`
+Für Ganzzahl-Berechnungen in nativer C-Geschwindigkeit:
+```bash
+SUMME=$(( 10 + 25 ))
+PROZENT=$(( (USED * 100) / TOTAL ))
+REST=$(( 17 % 5 )) # Modulo = 2
+```
+
+---
+
+## 🎯 Aufgaben in `aufgabe.sh`
+
+1. **TODO 1 (`setze_umgebungsvariable`)**: Exportiere eine Umgebungsvariable dynamisch und gib sie als `NAME=WERT` zurück.
+2. **TODO 2 (`generiere_system_kennung`)**: Baue per `$(...)` eine Kennung im Format `user@host_YYYY-MM`.
+3. **TODO 3 (`zerlege_dateipfad`)**: Extrahiere DIR, FILE, EXT und BASE ausschließlich mit nativer Bash Parameter Expansion (`${var#...}`, `${var%...}`).
+4. **TODO 4 (`ermittle_konfigurationswert`)**: Gib den übergebenen Wert oder den Fallback mit `${var:-fallback}` zurück.
+5. **TODO 5 (`berechne_disk_quota`)**: Berechne freien Speicher und prozentuale Belegung mit `$(( ... ))`.
+
+---
+
+## 🧪 Tests ausführen
+
+```bash
+bash test_aufgabe.sh
+```
diff --git a/courses/bash/03_variablen_environment_und_subshells/aufgabe.sh b/courses/bash/03_variablen_environment_und_subshells/aufgabe.sh
new file mode 100644
index 0000000..5375be2
--- /dev/null
+++ b/courses/bash/03_variablen_environment_und_subshells/aufgabe.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# 🐧 BASH 03: VARIABLEN, ENVIRONMENT, EXPANSION & ARITHMETIK
+# ==============================================================================
+
+# 🎯 TEILZIEL 1 (TODO 1): Exportiere eine Umgebungsvariable dynamisch.
+# Setze und exportiere "$1" mit dem Wert "$2" und gib den String "$1=$2" auf stdout aus.
+setze_umgebungsvariable() {
+ local name="$1"
+ local wert="$2"
+ # TODO: Exportiere die Variable und gib "NAME=WERT" aus
+ return 0
+}
+
+# 🎯 TEILZIEL 2 (TODO 2): Nutze Command Substitution $(...) für eine System-Kennung.
+# Kombiniere $(whoami), $(hostname) und $(date +%Y-%m) im Format:
+# "USER@HOST_YYYY-MM"
+generiere_system_kennung() {
+ # TODO: Erzeuge und gib die Kennung per Command Substitution $(...) aus
+ return 0
+}
+
+# 🎯 TEILZIEL 3 (TODO 3): Dateipfad mit reiner Bash Parameter Expansion zerlegen.
+# Extrahiere aus "$1" (z.B. "/var/log/nginx/access.log"):
+# - Verzeichnis (DIR): z.B. /var/log/nginx
+# - Dateiname (FILE): z.B. access.log
+# - Endung (EXT): z.B. log
+# - Basisname ohne Endung (BASE): z.B. access
+# Ausgabeformat: "DIR: | FILE: | EXT: | BASE: "
+# ⚠️ WICHTIG: Verwende keine externen Tools (kein basename, dirname, sed, awk)!
+zerlege_dateipfad() {
+ local voller_pfad="$1"
+ # TODO: Nutze ${pfad%/*}, ${pfad##*/}, ${datei##*.}, ${datei%.*}
+ return 0
+}
+
+# 🎯 TEILZIEL 4 (TODO 4): Parameter Expansion mit Standardwerten (${var:-default}).
+# Gib "$1" zurück. Wenn "$1" leer oder nicht gesetzt ist, gib "$2" (Fallback) zurück.
+ermittle_konfigurationswert() {
+ local wert="$1"
+ local fallback="$2"
+ # TODO: Nutze Parameter Expansion mit Default-Wert
+ return 0
+}
+
+# 🎯 TEILZIEL 5 (TODO 5): Quota-Berechnung mit Shell-Arithmetik $(( ... )).
+# Berechne aus "$1" (total_mb) und "$2" (used_mb):
+# - freier Speicher: total_mb - used_mb
+# - Prozentuale Belegung (Ganzzahl): (used_mb * 100) / total_mb
+# Ausgabeformat: "TOTAL: MB, USED: MB (%), FREE: MB"
+berechne_disk_quota() {
+ local total_mb="$1"
+ local used_mb="$2"
+ # TODO: Berechne freie MB und Prozent per $(( ... ))
+ return 0
+}
diff --git a/courses/bash/03_variablen_environment_und_subshells/index.html b/courses/bash/03_variablen_environment_und_subshells/index.html
new file mode 100644
index 0000000..5d65f52
--- /dev/null
+++ b/courses/bash/03_variablen_environment_und_subshells/index.html
@@ -0,0 +1,154 @@
+
+
+
+
+
+ Bash 03: Variablen, Environment & Subshells
+
+
+
+
+
+
+
+
🐧 Linux Bash & Cloud DevOps
+
Bash 03: Variablen, Environment & Subshells
+
Verstehe Prozess-Umgebungen, vererbe Variablen mit export, nutze Command Substitution und meistere native String-Parameter-Expansion.
+
+
+
+
+
+
+
🔐 1. Variablen, Vererbung & export
+
Variablen in der Shell werden ohne Leerzeichen um das Gleichheitszeichen herum zugewiesen. Mit export wandern sie in die Prozesstabelle aller Subprozesse:
+
+
# ⚠️ Keine Leerzeichen bei der Zuweisung!
+SERVICE="auth-api" # Lokal: Nur in dieser Shell verfügbar
+export PORT=8080 # Global: An alle Kind-Prozesse (z.B. node, python, skripte) vererbt
+
+# Die wichtigste Variable: $PATH
+echo $PATH # /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin
+export PATH="/opt/tools/bin:$PATH" # Suchpfad für eigene DevOps-Skripte erweitern
+
+
+
+
+
⚙️ 2. Parameter Expansion & Command Substitution
+
Moderne Shell-Programmierung vermeidet langsame externe Tool-Aufrufe, wo immer Bash es nativ lösen kann:
🧬 Didaktische Analogie: Vererbung in der Prozesstabelle
+
Lokale Variable vs. export: Eine lokale Variable ist wie ein Notizzettel in deiner Jackentasche – nur du kannst ihn lesen. Wenn du mit export arbeitest, machst du den Zettel zu einer DNA-Eigenschaft, die jedes Kind (jeder Subprozess) bei der Geburt automatisch mitbekommt!
Vermeide basename "$FILE" und dirname "$FILE" in Schleifen über Millionen Dateien! Native Parameter Expansion wie ${FILE##*/} läuft in Nanosekunden direkt im Bash-C-Core ohne Prozess-Forking.
Dateirechte (chmod 600): Das ist dein persönliches Bankschließfach. Nur du hast den Schlüssel (6=Read/Write), niemand sonst darf auch nur hineinsehen (0=No Access).
+
SIGTERM vs. SIGKILL:kill -15 ist das Klopfen an der Tür mit der Bitte, das Gebäude geordnet zu verlassen. kill -9 ist die Feuerwehr, die sofort die Sicherungen herausdreht und das Wasser abstellt!
+
+
+
+
+
+
📑 3. Cheat-Sheet: Rechte & Signale
+
+
+
+
+
Befehl
+
Syntax / Signal
+
Beschreibung
+
+
+
+
+
chmod 600
+
rw-------
+
Schutz für Private Keys & Secrets
+
+
+
chmod 755
+
rwxr-xr-x
+
Standard für ausführbare Skripte
+
+
+
&
+
cmd &
+
Startet Befehl asynchron im Hintergrund
+
+
+
$!
+
PID=$!
+
PID des zuletzt im Hintergrund gestarteten Prozesses
+
+
+
kill -0
+
kill -0 $PID
+
Testet Prozess-Existenz (kein Abbruch)
+
+
+
SIGTERM (15)
+
kill -15 $PID
+
Geordnetes Beenden mit Ressourcen-Cleanup
+
+
+
SIGKILL (9)
+
kill -9 $PID
+
Sofortiger harter Kernel-Abbruch
+
+
+
+
+
+
+
+
+
🎯 Aufgaben & Checkliste in aufgabe.sh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💡 DevOps Pro-Tipp: SSH Private Key Permissions
+
OpenSSH verweigert strikt die Verbindung, wenn deine Private Keys nicht auf chmod 600 stehen ("UNPROTECTED PRIVATE KEY FILE!"). Automatisiere diese Prüfung in deinen CI/CD-Pipelines!
🚦 Didaktische Analogie: Zollkontrolle am Server-Gateway
+
Die Testoperatoren:[[ -f ]] und [[ -x ]] sind wie der Sicherheitsdienst am Flughafentor – sie prüfen Pass (Existenz) und Visum (Ausführungsrecht). =~ ist der biometrische Scanner, der das genaue Muster der Ausweisnummer abgleicht!
+
+
+
+
+
+
📑 3. Cheat-Sheet: Testoperatoren
+
+
+
+
+
Operator
+
Syntax
+
Bedeutung
+
+
+
+
+
-f / -d / -e
+
[[ -f "$path" ]]
+
Reguläre Datei / Verzeichnis / Existiert
+
+
+
-x / -r / -w
+
[[ -x "$script" ]]
+
Ausführbar / Lesbar / Schreibbar
+
+
+
-z / -n
+
[[ -z "$str" ]]
+
String ist leer (Länge 0) / String nicht leer
+
+
+
=~
+
[[ "$val" =~ ^[0-9]+$ ]]
+
Regulärer Ausdruck (Speichert Gruppen in BASH_REMATCH)
+
+
+
(( a >= b ))
+
(( cpu >= 90 ))
+
Schneller C-Style Zahlenvergleich
+
+
+
$?
+
if [[ $? -eq 0 ]]; then
+
Exit-Code des unmittelbar zuvor ausgeführten Befehls
+
+
+
+
+
+
+
+
+
🎯 Aufgaben & Checkliste in aufgabe.sh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💡 Defensive Bash: Keine einfachen Klammern [ ... ]
+
Vermeide das alte POSIX [ "$a" = "$b" ], da nicht gesetzte Variablen zu Syntax-Fehlern (unary operator expected) führen. In [[ ... ]] ist Bash robuster gegen leere Variablen.
🏷️ Didaktische Analogie: Schuhkartons vs. Adressbuch
+
Indiziertes Array (arr[0]): Wie ein Stapel nummerierter Schuhkartons. Du findest Dinge nur über ihre Etagen-Nummer (0, 1, 2...).
+
Assoziatives Array (declare -A): Dein Adressbuch. Du suchst nicht nach Seite 4, sondern schlägst direkt das Stichwort "database" auf und hast sofort die IP-Adresse zur Hand!
+
+
+
+
+
+
📑 3. Cheat-Sheet: Arrays & getopts
+
+
+
+
+
Konstrukt
+
Beispiel
+
Zweck
+
+
+
+
+
declare -A map
+
declare -A conf=([a]=1 [b]=2)
+
Deklariert assoziatives Array (Key-Value)
+
+
+
${!map[@]}
+
for k in "${!map[@]}"; do
+
Iteriert über alle Keys der Map
+
+
+
${map[@]}
+
echo "${map[@]}"
+
Liefert alle Werte (Values)
+
+
+
while IFS= read -r
+
while IFS= read -r l; do
+
Standard für zeilenweises Einlesen
+
+
+
getopts "e:p:v" opt
+
./script.sh -e prod -p 443 -v
+
POSIX-konformes Parsing von CLI-Optionen
+
+
+
+
+
+
+
+
+
🎯 Aufgaben & Checkliste in aufgabe.sh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💡 Tipp: getopts & OPTIND Reset
+
Wenn du getopts innerhalb von Funktionen aufrufst, setze zu Beginn immer local OPTIND=1, damit nachfolgende Funktionsaufrufe den Parsing-Index von vorne beginnen!
⛏️ Didaktische Analogie: Das Bergwerk der Datenanalyse
+
grep: Dein Metalldetektor – er schlägt an, wenn eine Zeile das gesuchte Goldstück (Pattern) enthält.
+
sed: Dein Meißel – er schleift und poliert Zeichenketten in Form.
+
awk: Deine Buchhalter-Waage – wiegt Spalten ab und berechnet Summen und Schnitte.
+
jq: Der Röntgen-Präzisionslaser, der blitzschnell durch verschachtelte JSON-Kristallstrukturen schneidet!
+
+
+
+
+
+
📑 3. Cheat-Sheet: Textwerkzeuge
+
+
+
+
+
Tool
+
Beispiel
+
Einsatzzweck
+
+
+
+
+
grep -E -i
+
grep -E -i '(err|crit)' log
+
Case-insensitiver Extended-Regex-Filter
+
+
+
sed -E 's/a/b/g'
+
sed -E 's/http:/https:/g'
+
Stream-Editor zum Ersetzen und Transformieren
+
+
+
awk '{ sum += $NF }'
+
awk '{ print $1 }' file
+
Spaltenextraktion, Filter und Aggregation
+
+
+
jq -r '.[] | select(...)'
+
jq -r '.[] | .hostname'
+
JSON-Parsing, Filterung & CSV-Export
+
+
+
+
+
+
+
+
+
🎯 Aufgaben & Checkliste in aufgabe.sh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💡 DevOps Pro-Tipp: jq Raw Output (-r)
+
Verwende beim Extrahieren von Strings für Umgebungsvariablen oder Shell-Skripte immer das Flag -r (raw output), damit jq keine störenden Anführungszeichen um den Text ausgibt!
+
+
diff --git a/courses/bash/07_text_mining_grep_sed_awk_und_jq/musterloesung.sh b/courses/bash/07_text_mining_grep_sed_awk_und_jq/musterloesung.sh
new file mode 100644
index 0000000..e29989f
--- /dev/null
+++ b/courses/bash/07_text_mining_grep_sed_awk_und_jq/musterloesung.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# 🐧 BASH 07: MUSTERLÖSUNG
+# ==============================================================================
+
+extrahiere_fehler_logs() {
+ local log_text="$1"
+ echo "$log_text" | grep -E -i '(error|critical|fatal)' | grep -v -i 'debug'
+}
+
+maskiere_passwoerter_sed() {
+ local config_text="$1"
+ echo "$config_text" | sed -E \
+ -e 's/password="[^"]*"/password="***REDACTED***"/g' \
+ -e 's/api_key="[^"]*"/api_key="***REDACTED***"/g'
+}
+
+berechne_weblog_traffic_awk() {
+ local log_text="$1"
+ echo "$log_text" | awk '
+ NF >= 1 {
+ sum += $NF
+ count++
+ }
+ END {
+ if (count > 0) {
+ avg = int(sum / count)
+ print "REQUESTS: " count ", BYTES: " sum ", AVG: " avg
+ }
+ }
+ '
+}
+
+filtere_aktive_benutzer_jq() {
+ local json_text="$1"
+ echo "$json_text" | jq -r '.[] | select(.active == true) | .username'
+}
+
+transformiere_server_json_zu_csv() {
+ local json_text="$1"
+ echo "$json_text" | jq -r '.[] | "\(.hostname),\(.ip),\(.status)"'
+}
diff --git a/courses/bash/07_text_mining_grep_sed_awk_und_jq/test_aufgabe.sh b/courses/bash/07_text_mining_grep_sed_awk_und_jq/test_aufgabe.sh
new file mode 100644
index 0000000..0328e48
--- /dev/null
+++ b/courses/bash/07_text_mining_grep_sed_awk_und_jq/test_aufgabe.sh
@@ -0,0 +1,136 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# 🧪 BATS-INSPIRIERTE TESTSUITE: BASH 07 - TEXT-MINING (GREP, SED, AWK & JQ)
+# ==============================================================================
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+TARGET_SCRIPT="${1:-aufgabe.sh}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# JQ Emulation Fallback falls jq nicht systemweit installiert ist
+if ! command -v jq >/dev/null 2>&1; then
+ jq() {
+ python3 -c '
+import sys, json
+
+args = sys.argv[1:]
+query = [a for a in args if not a.startswith("-")][0] if any(not a.startswith("-") for a in args) else "."
+data = json.load(sys.stdin)
+
+if "active == true" in query:
+ for item in data:
+ if item.get("active") is True:
+ print(item.get("username", ""))
+elif "hostname" in query:
+ for item in data:
+ print("%s,%s,%s" % (item.get("hostname"), item.get("ip"), item.get("status")))
+elif query == ".":
+ print(json.dumps(data, indent=2))
+' "$@"
+ }
+ export -f jq 2>/dev/null || true
+fi
+
+TOTAL_TESTS=0
+PASSED_TESTS=0
+FAILED_TESTS=0
+
+echo -e "${BLUE}================================================================${NC}"
+echo -e "${BLUE}🧪 TESTSUITE: Bash 07 - Text-Mining mit grep, sed, awk & jq${NC}"
+echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
+echo -e "${BLUE}================================================================${NC}\n"
+
+if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
+ echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
+ exit 1
+fi
+
+source "$SCRIPT_DIR/$TARGET_SCRIPT"
+
+run_test() {
+ local test_name="$1"
+ local test_cmd="$2"
+ ((TOTAL_TESTS++))
+
+ echo -e "▶ Test ${TOTAL_TESTS}: ${test_name}"
+ if eval "$test_cmd"; then
+ echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
+ ((PASSED_TESTS++))
+ else
+ echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
+ ((FAILED_TESTS++))
+ fi
+}
+
+# TEST: Teilziel 1 - extrahiere_fehler_logs
+run_test "Fehler-Logs mit grep filtern und Debug ignorieren" '
+ LOGS="2026-08-30 [INFO] System gestartet
+2026-08-30 [ERROR] Datenbank nicht erreichbar
+2026-08-30 [DEBUG] ERROR 404 in debug context
+2026-08-30 [CRITICAL] Out of Memory Exception"
+ RES=$(extrahiere_fehler_logs "$LOGS")
+ [ "$(echo "$RES" | wc -l)" -eq 2 ] && \
+ echo "$RES" | grep -q "Datenbank nicht erreichbar" && \
+ echo "$RES" | grep -q "Out of Memory Exception" && \
+ ! echo "$RES" | grep -q "debug context"
+'
+
+# TEST: Teilziel 2 - maskiere_passwoerter_sed
+run_test "Passwoerter und API-Keys mit sed maskieren" '
+ CONF="db_user=\"admin\" password=\"SuperSecret123!\"
+api_key=\"sk-live-999abc\" timeout=30"
+ MASKED=$(maskiere_passwoerter_sed "$CONF")
+ echo "$MASKED" | grep -q "password=\"\*\*\*REDACTED\*\*\*\"" && \
+ echo "$MASKED" | grep -q "api_key=\"\*\*\*REDACTED\*\*\*\"" && \
+ ! echo "$MASKED" | grep -q "SuperSecret" && \
+ ! echo "$MASKED" | grep -q "sk-live-999abc"
+'
+
+# TEST: Teilziel 3 - berechne_weblog_traffic_awk
+run_test "Weblog-Statistiken mit awk aggregieren" '
+ ACCESS="192.168.1.1 - - [30/Aug/2026:12:00:01] \"GET /api/v1 HTTP/1.1\" 200 1000
+192.168.1.2 - - [30/Aug/2026:12:00:02] \"GET /index.html HTTP/1.1\" 200 2000
+192.168.1.3 - - [30/Aug/2026:12:00:03] \"GET /style.css HTTP/1.1\" 200 3000"
+ STAT=$(berechne_weblog_traffic_awk "$ACCESS")
+ [ "$STAT" = "REQUESTS: 3, BYTES: 6000, AVG: 2000" ]
+'
+
+# TEST: Teilziel 4 - filtere_aktive_benutzer_jq
+run_test "Aktive Benutzer aus JSON mit jq extrahieren" '
+ JSON="[
+ {\"username\": \"alice\", \"active\": true},
+ {\"username\": \"bob\", \"active\": false},
+ {\"username\": \"charlie\", \"active\": true}
+ ]"
+ ACTIVE=$(filtere_aktive_benutzer_jq "$JSON")
+ [ "$(echo "$ACTIVE" | xargs)" = "alice charlie" ]
+'
+
+# TEST: Teilziel 5 - transformiere_server_json_zu_csv
+run_test "JSON-Serverliste in CSV umwandeln mit jq" '
+ SERVERS="[
+ {\"hostname\": \"srv-web1\", \"ip\": \"10.0.1.10\", \"status\": \"running\"},
+ {\"hostname\": \"srv-db1\", \"ip\": \"10.0.2.20\", \"status\": \"standby\"}
+ ]"
+ CSV=$(transformiere_server_json_zu_csv "$SERVERS")
+ LINE1=$(echo "$CSV" | head -n 1)
+ LINE2=$(echo "$CSV" | tail -n 1)
+ [ "$LINE1" = "srv-web1,10.0.1.10,running" ] && \
+ [ "$LINE2" = "srv-db1,10.0.2.20,standby" ]
+'
+
+echo -e "${BLUE}================================================================${NC}"
+if [ "$FAILED_TESTS" -eq 0 ]; then
+ echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in Bash 07 erfolgreich bestanden!${NC}"
+ echo -e "${BLUE}================================================================${NC}"
+ exit 0
+else
+ echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
+ echo -e "${BLUE}================================================================${NC}"
+ exit 1
+fi
diff --git a/courses/bash/08_robuste_skripte_und_debugging/README.md b/courses/bash/08_robuste_skripte_und_debugging/README.md
new file mode 100644
index 0000000..79e4d83
--- /dev/null
+++ b/courses/bash/08_robuste_skripte_und_debugging/README.md
@@ -0,0 +1,54 @@
+# Bash 08: Robuste Skripte, Strict Mode & Debugging 🛡️
+
+Willkommen zu **Modul 08** (Abschluss von Lehrpfad 2) des Linux Bash & DevOps Lehrpfads!
+
+In diesem Modul lernst du, wie professionelle DevOps-Engineers ausfallsichere, unzerstörbare Shell-Skripte für Produktivumgebungen schreiben: Der **Bash Strict Mode (`set -euo pipefail`)**, Signal-Traps (`trap ... EXIT`), Concurrency-Locks mit `flock`, strukturiertes Logging und Tracing mit `set -x`.
+
+---
+
+## 💡 1. Das Wichtigste in Kürze
+
+### Der unschlagbare Bash Strict Mode
+Standardmäßig ignoriert Bash Fehler und macht einfach weiter – was in Produktionsskripten verheerend sein kann. Der Strict Mode stoppt sofort:
+```bash
+#!/usr/bin/env bash
+set -euo pipefail
+IFS=$'\n\t'
+```
+- **`set -e`** (errexit): Bricht das Skript sofort ab, wenn ein Befehl fehlschlägt (`Exit != 0`).
+- **`set -u`** (nounset): Behandelt ungesetzte Variablen als schweren Fehler (schützt vor Katastrophen wie `rm -rf /tmp/$SUBDIR/` wenn `$SUBDIR` leer ist!).
+- **`set -o pipefail`**: Die Pipeline schlägt fehl, wenn *irgendein* Teil fehlschlägt (nicht nur der letzte).
+
+### Cleanup mit `trap` & `mktemp`
+Egal ob ein Skript erfolgreich endet, crasht oder mit STRG+C abgebrochen wird – der `EXIT`-Trap räumt immer zuverlässig auf:
+```bash
+TEMP_DIR=$(mktemp -d)
+trap 'rm -rf "$TEMP_DIR"' EXIT
+```
+
+### Exklusive Locks mit `flock`
+Verhindert, dass ein Cronjob oder Worker mehrfach parallel startet:
+```bash
+flock -n /var/lock/backup.lock -c "/opt/scripts/do_backup.sh" || {
+ echo "Backup läuft bereits!" >&2
+ exit 1
+}
+```
+
+---
+
+## 🎯 Aufgaben in `aufgabe.sh`
+
+1. **TODO 1 (`aktiviere_strict_mode`)**: Konfiguriere `set -euo pipefail`.
+2. **TODO 2 (`devops_logger`)**: Formatiere Logzeilen mit Timestamp und trenne Fehlerkanäle (ERROR/FATAL auf stderr, INFO auf stdout).
+3. **TODO 3 (`erstelle_temp_verzeichnis_mit_cleanup`)**: Erstelle mit `mktemp -d` ein Verzeichnis und sichere es mit `trap 'rm -rf ...' EXIT`.
+4. **TODO 4 (`fuehre_exklusiv_mit_lock_aus`)**: Schütze Befehlsausführungen mit `flock -n`.
+5. **TODO 5 (`debug_trace_ausfuehren`)**: Aktiviere temporär `set -x` für Tracing und stelle den Zustand mit `set +x` wieder her.
+
+---
+
+## 🧪 Tests ausführen
+
+```bash
+bash test_aufgabe.sh
+```
diff --git a/courses/bash/08_robuste_skripte_und_debugging/aufgabe.sh b/courses/bash/08_robuste_skripte_und_debugging/aufgabe.sh
new file mode 100644
index 0000000..a076024
--- /dev/null
+++ b/courses/bash/08_robuste_skripte_und_debugging/aufgabe.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# 🐧 BASH 08: ROBUSTE SKRIPTE, STRICT MODE, TRAPS & DEBUGGING
+# ==============================================================================
+
+# 🎯 TEILZIEL 1 (TODO 1): Strict Mode aktivieren.
+# Aktiviere `set -euo pipefail` und gib "STRICT_MODE_ON" aus.
+aktiviere_strict_mode() {
+ # TODO: Aktiviere set -euo pipefail
+ echo "STRICT_MODE_ON"
+}
+
+# 🎯 TEILZIEL 2 (TODO 2): Strukturierter DevOps-Logger mit Stream-Trennung.
+# Formatiere die Ausgabe als: `[$(date +'%Y-%m-%d %H:%M:%S')] [$1] $2`
+# - Wenn $1 == "ERROR" oder $1 == "FATAL": Leite die Ausgabe nach stderr (FD 2) um.
+# - Für alle anderen Levels (INFO, WARN, DEBUG): Leite nach stdout (FD 1) um.
+devops_logger() {
+ local level="$1"
+ local meldung="$2"
+ # TODO: Formatiere Zeitstempel und trenne stdout/stderr
+ return 0
+}
+
+# 🎯 TEILZIEL 3 (TODO 3): Sichere temporäre Ressourcen mit `trap` & `mktemp`.
+# Erstelle ein temporäres Verzeichnis mit `mktemp -d /tmp/devops_XXXXXX`
+# und richte einen `trap 'rm -rf "$temp_dir"' EXIT` ein.
+# Gib den Pfad des temporären Verzeichnisses auf stdout aus.
+erstelle_temp_verzeichnis_mit_cleanup() {
+ # TODO: Erstelle Verzeichnis mit mktemp -d und registriere EXIT trap
+ return 0
+}
+
+# 🎯 TEILZIEL 4 (TODO 4): Exklusive Lock-Ausführung mit `flock`.
+# Führe "$2" (Befehl) geschützt durch die Lock-Datei "$1" mit `flock -n` (non-blocking) aus.
+# - Wenn Lock erfolgreich: Gib den Exit-Code des Befehls zurück.
+# - Wenn Lock blockiert ist: Schreibe "RESOURCE_LOCKED" nach stderr und gib Exit-Code 1 zurück.
+fuehre_exklusiv_mit_lock_aus() {
+ local lock_file="$1"
+ local befehl="$2"
+ # TODO: Nutze flock -n "$lock_file" -c "$befehl"
+ return 0
+}
+
+# 🎯 TEILZIEL 5 (TODO 5): Debug Trace Modus temporär steuern.
+# Aktiviere temporär den Bash-Trace-Modus `set -x`, führe "$1" aus und deaktiviere mit `set +x`.
+debug_trace_ausfuehren() {
+ local befehl="$1"
+ # TODO: Schalte set -x ein, führe befehl aus, schalte mit set +x aus
+ return 0
+}
diff --git a/courses/bash/08_robuste_skripte_und_debugging/index.html b/courses/bash/08_robuste_skripte_und_debugging/index.html
new file mode 100644
index 0000000..0c78404
--- /dev/null
+++ b/courses/bash/08_robuste_skripte_und_debugging/index.html
@@ -0,0 +1,150 @@
+
+
+
+
+
+ Bash 08: Robuste Skripte (set -euo pipefail)
+
+
+
+
+
+
+
+
⚡ Lehrpfad 2: Scripting & Text-Mining
+
Bash 08: Robuste Skripte, Strict Mode & Debugging
+
Härte deine Shell-Skripte für den 24/7-Serverbetrieb mit set -euo pipefail, automatischen Cleanup-Traps und Concurrency-Locks mit flock.
+
+
+
+
+
+
+
🛡️ 1. Der Bash Strict Mode (set -euo pipefail)
+
In Produktions-Umgebungen ist "Stiller Weiterlauf bei Fehlern" die häufigste Ursache für Datenverlust. Der Strict Mode stoppt das Skript deterministisch beim ersten Fehler:
+
+
#!/usr/bin/env bash
+set -euo pipefail
+IFS=$'\n\t'
+
+# 1. set -e (errexit): Bricht bei unhandled Exit-Codes != 0 sofort ab
+# 2. set -u (nounset): Behandelt Zugriffe auf ungesetzte Variablen als Fatal Error
+# 3. set -o pipefail: Ein Fehler in foo | bar | baz lässt die gesamte Pipeline scheitern
+
+# Erlaubte Fehler sauber abfangen:
+optional_cleanup || true
+
+
+
+
+
🧹 2. Signal-Traps, Cleanup & File Locks (flock)
+
Hinterlasse niemals Datenmüll im Dateisystem und verhindere Race Conditions bei parallel laufenden Cronjobs:
+
+
# 🎯 1. EXIT Trap – Garantiertes Aufräumen:
+WORK_DIR=$(mktemp -d)
+trap 'rm -rf "$WORK_DIR"' EXIT
+
+# 🎯 2. Exklusives Locking gegen Doppel-Ausführung:
+exec 200>/var/lock/backup.lock
+flock -n 200 || { echo "Job läuft bereits!" >&2; exit 1; }
+
+# 🎯 3. Trace-Debugging im laufenden Betrieb:
+set -x # Zeigt jeden ausgeführten Befehl mit '+' an
+grep "root" /etc/passwd
+set +x # Schaltet Tracing wieder aus
set -euo pipefail: Das Notbremssystem im Hochgeschwindigkeitszug. Sobald eine Variable wackelt oder ein Zwischenhalt entgleist, stoppt der Zug sofort, statt ungebremst in den Abgrund zu rasen.
+
flock: Das "Besetzt"-Schild am Hotelzimmer. Kein zweiter Gast kann eintreten, solange die Tür verriegelt ist!
+
+
+
+
+
+
📑 3. Cheat-Sheet: Defensive Bash
+
+
+
+
+
Befehl / Option
+
Syntax
+
Schutzwirkung
+
+
+
+
+
set -e
+
set -o errexit
+
Sofortiger Abbruch bei Befehlsfehlern
+
+
+
set -u
+
set -o nounset
+
Verhindert Ausführung mit leeren/ungesetzten Variablen
+
+
+
set -o pipefail
+
cmd1 | cmd2
+
Gibt Fehlercode zurück, falls irgendein Glied der Pipe fehlschlägt
+
+
+
trap '...' EXIT
+
trap cleanup EXIT
+
Führt Aufräumcode bei normalem und abnormalem Beenden aus
+
+
+
flock -n <file>
+
flock -n /tmp/job.lock cmd
+
Non-blocking Concurrency Lock gegen parallele Runs
Ohne set -u führt ein unbedachter Aufruf wie rm -rf /var/app/$ENV_NAME/cache bei leerer Variable zu rm -rf /var/app//cache. Mit set -u bricht die Shell sofort vor dem Löschbefehl ab!
+
+
diff --git a/courses/bash/08_robuste_skripte_und_debugging/musterloesung.sh b/courses/bash/08_robuste_skripte_und_debugging/musterloesung.sh
new file mode 100644
index 0000000..3b04db0
--- /dev/null
+++ b/courses/bash/08_robuste_skripte_und_debugging/musterloesung.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# 🐧 BASH 08: MUSTERLÖSUNG
+# ==============================================================================
+
+aktiviere_strict_mode() {
+ set -euo pipefail
+ echo "STRICT_MODE_ON"
+}
+
+devops_logger() {
+ local level="$1"
+ local meldung="$2"
+ local timestamp
+ timestamp="$(date +'%Y-%m-%d %H:%M:%S')"
+ local log_line="[$timestamp] [$level] $meldung"
+
+ case "$level" in
+ ERROR|FATAL)
+ echo "$log_line" >&2
+ ;;
+ *)
+ echo "$log_line"
+ ;;
+ esac
+}
+
+erstelle_temp_verzeichnis_mit_cleanup() {
+ local temp_dir
+ temp_dir=$(mktemp -d /tmp/devops_XXXXXX)
+ trap 'rm -rf "$temp_dir"' EXIT
+ echo "$temp_dir"
+}
+
+fuehre_exklusiv_mit_lock_aus() {
+ local lock_file="$1"
+ local befehl="$2"
+
+ if flock -n "$lock_file" -c "$befehl"; then
+ return 0
+ else
+ echo "RESOURCE_LOCKED" >&2
+ return 1
+ fi
+}
+
+debug_trace_ausfuehren() {
+ local befehl="$1"
+ set -x
+ eval "$befehl"
+ { set +x; } 2>/dev/null
+}
diff --git a/courses/bash/08_robuste_skripte_und_debugging/test_aufgabe.sh b/courses/bash/08_robuste_skripte_und_debugging/test_aufgabe.sh
new file mode 100644
index 0000000..c1feb0e
--- /dev/null
+++ b/courses/bash/08_robuste_skripte_und_debugging/test_aufgabe.sh
@@ -0,0 +1,121 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# 🧪 BATS-INSPIRIERTE TESTSUITE: BASH 08 - ROBUSTE SKRIPTE & STRICT MODE
+# ==============================================================================
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+TARGET_SCRIPT="${1:-aufgabe.sh}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+TOTAL_TESTS=0
+PASSED_TESTS=0
+FAILED_TESTS=0
+
+echo -e "${BLUE}================================================================${NC}"
+echo -e "${BLUE}🧪 TESTSUITE: Bash 08 - Robuste Skripte, Strict Mode & Traps${NC}"
+echo -e "${BLUE} Ziel-Skript: ${YELLOW}${TARGET_SCRIPT}${NC}"
+echo -e "${BLUE}================================================================${NC}\n"
+
+if [ ! -f "$SCRIPT_DIR/$TARGET_SCRIPT" ]; then
+ echo -e "${RED}❌ Fehler: Datei '$TARGET_SCRIPT' nicht gefunden!${NC}"
+ exit 1
+fi
+
+source "$SCRIPT_DIR/$TARGET_SCRIPT"
+
+TEST_ROOT=$(mktemp -d /tmp/bash08_test_XXXXXX)
+trap 'rm -rf "$TEST_ROOT"' EXIT
+
+run_test() {
+ local test_name="$1"
+ local test_cmd="$2"
+ ((TOTAL_TESTS++))
+
+ echo -e "▶ Test ${TOTAL_TESTS}: ${test_name}"
+ if eval "$test_cmd"; then
+ echo -e " ${GREEN}✓ BESTANDEN${NC}\n"
+ ((PASSED_TESTS++))
+ else
+ echo -e " ${RED}✗ FEHLGESCHLAGEN${NC}\n"
+ ((FAILED_TESTS++))
+ fi
+}
+
+# TEST: Teilziel 1 - aktiviere_strict_mode
+run_test "Strict Mode Aktivierung (set -euo pipefail)" '
+ bash -c "
+ source \"$SCRIPT_DIR/$TARGET_SCRIPT\"
+ aktiviere_strict_mode >/dev/null
+ # Unset-Variable soll zum sofortigen Abbruch fuehren
+ if [ -o nounset ] && [ -o errexit ]; then
+ exit 0
+ else
+ exit 1
+ fi
+ "
+'
+
+# TEST: Teilziel 2 - devops_logger
+run_test "DevOps-Logger mit Zeitstempel und Stream-Trennung" '
+ OUT_LOG="$TEST_ROOT/out.log"
+ ERR_LOG="$TEST_ROOT/err.log"
+
+ devops_logger "INFO" "Dienst gestartet" > "$OUT_LOG" 2> "$ERR_LOG"
+ grep -q "\[INFO\] Dienst gestartet" "$OUT_LOG" && \
+ [ ! -s "$ERR_LOG" ] && \
+
+ devops_logger "ERROR" "Kritischer Verbindungsabbruch" > "$OUT_LOG" 2> "$ERR_LOG" && \
+ grep -q "\[ERROR\] Kritischer Verbindungsabbruch" "$ERR_LOG"
+'
+
+# TEST: Teilziel 3 - erstelle_temp_verzeichnis_mit_cleanup
+run_test "Temporaeres Verzeichnis mit mktemp anlegen" '
+ TEMP_DIR=$(erstelle_temp_verzeichnis_mit_cleanup)
+ [ -d "$TEMP_DIR" ] && [[ "$TEMP_DIR" =~ ^/tmp/devops_ ]]
+'
+
+# TEST: Teilziel 4 - fuehre_exklusiv_mit_lock_aus
+run_test "Exklusive File-Locks mit flock verwalten" '
+ LOCK_FILE="$TEST_ROOT/test.lock"
+ # 1. Normaler Aufruf muss erfolgreich sein
+ fuehre_exklusiv_mit_lock_aus "$LOCK_FILE" "echo Job1_Done" > "$TEST_ROOT/job1.out" && \
+ grep -q "Job1_Done" "$TEST_ROOT/job1.out" && \
+
+ # 2. Wenn Lock blockiert ist, muss Fehler gemeldet werden
+ bash -c "
+ source \"$SCRIPT_DIR/$TARGET_SCRIPT\"
+ (
+ flock 200
+ sleep 0.5
+ ) 200>\"$LOCK_FILE\" &
+ BG_PID=\$!
+ sleep 0.1
+ fuehre_exklusiv_mit_lock_aus \"$LOCK_FILE\" \"echo Should_Not_Run\" 2>\"$TEST_ROOT/lock_err.log\"
+ RET=\$?
+ wait \$BG_PID 2>/dev/null || true
+ [ \$RET -ne 0 ] && grep -q \"RESOURCE_LOCKED\" \"$TEST_ROOT/lock_err.log\"
+ "
+'
+
+# TEST: Teilziel 5 - debug_trace_ausfuehren
+run_test "Debug Trace mit set -x temporaer ausfuehren" '
+ TRACE_ERR="$TEST_ROOT/trace.err"
+ debug_trace_ausfuehren "echo TRACE_TEST" 2> "$TRACE_ERR" > /dev/null
+ grep -q "+ " "$TRACE_ERR" || grep -q "echo TRACE_TEST" "$TRACE_ERR"
+'
+
+echo -e "${BLUE}================================================================${NC}"
+if [ "$FAILED_TESTS" -eq 0 ]; then
+ echo -e "${GREEN}🎉 ERFOLG: Alle ${PASSED_TESTS}/${TOTAL_TESTS} Tests in Bash 08 erfolgreich bestanden!${NC}"
+ echo -e "${BLUE}================================================================${NC}"
+ exit 0
+else
+ echo -e "${RED}❌ FEHLER: ${FAILED_TESTS} von ${TOTAL_TESTS} Tests fehlgeschlagen.${NC}"
+ echo -e "${BLUE}================================================================${NC}"
+ exit 1
+fi
diff --git a/courses/bash/09_parallelisierung_mit_xargs_und_parallel/README.md b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/README.md
new file mode 100644
index 0000000..a615a85
--- /dev/null
+++ b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/README.md
@@ -0,0 +1,87 @@
+# Bash 09: Multithreading & Parallelisierung im Terminal 🚀
+
+Willkommen zu **Modul 09** des Bash & Cloud DevOps Kurses! In modernen Server- und Cloud-Umgebungen müssen Skripte oft hunderte oder tausende I/O- oder CPU-Aufgaben gleichzeitig ausführen (z.B. Log-Analysen, API-Pollings, Dateitransfers, Bildkompression).
+
+In diesem Modul meisterst du die Kunst der Parallelisierung direkt auf der Linux-Kommandozeile.
+
+---
+
+## 💡 1. Das Wichtigste in Kürze
+
+### 1.1 `xargs -P ` (Parallele Ausführung mit Standard-Tools)
+`xargs` liest Einträge von `stdin` und führt Befehle aus. Mit dem Parameter `-P ` (oder `--max-procs`) bestimmst du, wie viele Prozesse parallel laufen:
+```bash
+# Verarbeitet URLs parallel mit maximal 8 Threads:
+cat urls.txt | xargs -P 8 -I {} curl -s -O "{}"
+
+# Null-terminierte Strings für sichere Dateinamen mit Leerzeichen:
+find . -type f -name "*.log" -print0 | xargs -0 -P 4 gzip
+```
+
+### 1.2 GNU `parallel` (Die Königsklasse der Shell-Parallelisierung)
+GNU `parallel` bietet erweiterte Features wie Job-Tagging, CPU-Kern-Erkennung (`-j+0`), Fortschrittsbalken (`--bar`) und strukturierte Ausgabe (`--keep-order`):
+```bash
+# Führt Bildoptimierung auf allen CPU-Kernen parallel aus:
+parallel -j $(nproc) --bar "convert {} -resize 800x600 opt_{}" ::: *.jpg
+```
+
+### 1.3 Native Bash Background Workers (`&`, `$!`, `wait`)
+Reine Bash-Mittel ohne externe Werkzeuge für maximale Portabilität:
+```bash
+pids=()
+
+for server in web01 web02 db01; do
+ (
+ echo "Prüfe $server..."
+ ping -c 1 "$server" > /dev/null 2>&1
+ echo "Fertig: $server"
+ ) &
+ pids+=($!)
+done
+
+# Warten auf alle Prozesse:
+for pid in "${pids[@]}"; do
+ wait "$pid"
+done
+echo "Alle Server geprüft!"
+```
+
+### 1.4 Concurrency Throttling (Worker Pool)
+Um das System vor Überlastung (Too many open files / Out of Memory) zu schützen, drosseln wir die Anzahl aktiver Prozesse:
+```bash
+MAX_JOBS=4
+for item in $(cat items.txt); do
+ process_item "$item" &
+
+ # Solange MAX_JOBS erreicht sind, kurz warten
+ while [ $(jobs -rp | wc -l) -ge "$MAX_JOBS" ]; do
+ sleep 0.05
+ done
+done
+wait
+```
+
+---
+
+## 🎯 Aufgaben in `aufgabe.sh`
+
+1. **`parallel_xargs_process(input_file, max_procs)`**:
+ Liest eine Textdatei und gibt für jede Zeile `PROCESSED: ` über `xargs -P` parallel aus.
+2. **`background_workers_wait(task1, task2, ...)`**:
+ Startet Hintergrund-Subshells mit `&`, sammelt deren PIDs in einem Array und synchronisiert mit `wait`.
+3. **`parallel_batch_calculator(numbers_file, max_procs)`**:
+ Liest Zahlen aus einer Datei, berechnet deren Quadrat parallel und liefert das Ergebnis numerisch sortiert zurück (`^2 = `).
+4. **`throttle_concurrent_jobs(total_jobs, max_concurrent)`**:
+ Implementiert einen gedrosselten Worker-Pool, sodass niemals mehr als `max_concurrent` Prozesse gleichzeitig im Hintergrund laufen.
+
+---
+
+## 🧪 Tests ausführen
+
+```bash
+# Eigene Lösung testen:
+bash test_aufgabe.sh
+
+# Musterlösung testen:
+bash test_aufgabe.sh musterloesung.sh
+```
diff --git a/courses/bash/09_parallelisierung_mit_xargs_und_parallel/aufgabe.sh b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/aufgabe.sh
new file mode 100644
index 0000000..e6e62bd
--- /dev/null
+++ b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/aufgabe.sh
@@ -0,0 +1,110 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# Bash 09: Parallelisierung mit xargs, GNU parallel & Background Workers
+# ==============================================================================
+# In diesem Modul lernst du, rechen- oder I/O-intensive Aufgaben im Terminal
+# parallel auf mehreren CPU-Cores abzuarbeiten.
+# ==============================================================================
+
+# ------------------------------------------------------------------------------
+# TODO 1: Parallele Verarbeitung von Dateizeilen mit xargs
+# ------------------------------------------------------------------------------
+# Implementiere die Funktion `parallel_xargs_process`.
+# Parameter:
+# $1: Pfad zu einer Datei mit Einträgen (eine Zeile pro Item)
+# $2: Maximale Anzahl paralleler Prozesse (z.B. 4)
+# Verhalten:
+# - Liest jede Zeile aus $1
+# - Verarbeitet jedes Item parallel mit `xargs -n 1 -P "$2"`
+# - Für jedes Item soll ausgegeben werden: "PROCESSED: "
+# - Bei fehlender oder nicht existierender Datei: Return 1
+# ------------------------------------------------------------------------------
+parallel_xargs_process() {
+ local input_file="${1:-}"
+ local max_procs="${2:-2}"
+
+ # TODO: Prüfe ob input_file existiert, ansonsten return 1
+ # TODO: Nutze cat/xargs mit -n 1 und -P "$max_procs", um jedes Item mit "PROCESSED: $item" auszugeben
+ echo "TODO: Implementiere parallel_xargs_process"
+ return 1
+}
+
+# ------------------------------------------------------------------------------
+# TODO 2: Background Workers mit PID-Tracking & wait
+# ------------------------------------------------------------------------------
+# Implementiere die Funktion `background_workers_wait`.
+# Parameter:
+# $@: Eine Liste von Aufgabennamen (z.B. "taskA" "taskB" "taskC")
+# Verhalten:
+# - Startet für jeden übergebenen Aufgabennamen einen Hintergrundjob (`&`),
+# der folgendes ausgibt: "WORKER_START: " und nach einer kurzen
+# Operation (z.B. sleep 0.01) ausgibt: "WORKER_DONE: "
+# - Sammelt die PIDs aller gestarteten Jobs in einem Bash-Array (`pids+=($!)`).
+# - Wartet mit `wait` auf das Ende aller Hintergrundjobs.
+# - Gibt nach erfolgreichem Warten aus: "ALL_WORKERS_FINISHED"
+# - Liefert Return-Code 0.
+# ------------------------------------------------------------------------------
+background_workers_wait() {
+ local tasks=("$@")
+
+ # TODO: Prüfe ob Aufgaben übergeben wurden, wenn nicht return 1
+ # TODO: Starte für jede Task einen Background-Prozess (&) und tracke $!
+ # TODO: Warte mit `wait "${pids[@]}"`
+ # TODO: Gib "ALL_WORKERS_FINISHED" aus
+ echo "TODO: Implementiere background_workers_wait"
+ return 1
+}
+
+# ------------------------------------------------------------------------------
+# TODO 3: Paralleler Batch-Rechner (Numerische Transformation)
+# ------------------------------------------------------------------------------
+# Implementiere die Funktion `parallel_batch_calculator`.
+# Parameter:
+# $1: Pfad zu einer Datei mit Ganzzahlen (eine Zahl pro Zeile)
+# $2: Anzahl paralleler Worker (Default: 4)
+# Verhalten:
+# - Berechnet für jede Zahl n das Quadrat (n * n).
+# - Führt die Berechnung parallel via xargs oder Background-Workers durch.
+# - Gibt das Ergebnis im Format "^2 = " aus, sortiert nach numerisch aufsteigend.
+# - Beispiel Eingabe: 3, 5, 2
+# - Ausgabe:
+# 2^2 = 4
+# 3^2 = 9
+# 5^2 = 25
+# - Bei ungültiger Datei return 1.
+# ------------------------------------------------------------------------------
+parallel_batch_calculator() {
+ local numbers_file="${1:-}"
+ local max_procs="${2:-4}"
+
+ # TODO: Validiere Datei
+ # TODO: Berechne Quadrate parallel und gebe sie numerisch sortiert aus
+ echo "TODO: Implementiere parallel_batch_calculator"
+ return 1
+}
+
+# ------------------------------------------------------------------------------
+# TODO 4: Throttling & Concurrency Pool (Max. N aktive Jobs)
+# ------------------------------------------------------------------------------
+# Implementiere die Funktion `throttle_concurrent_jobs`.
+# Parameter:
+# $1: Gesamtanzahl der zu startenden Jobs (z.B. 6)
+# $2: Maximale Anzahl gleichzeitig laufender Jobs (z.B. 2)
+# Verhalten:
+# - Startet Jobs 1 bis $1 im Hintergrund.
+# - Jeder Job gibt aus: "JOB_RUNNING: "
+# - Stellt sicher, dass zu KEINEM Zeitpunkt mehr als $2 Jobs gleichzeitig laufen.
+# (Tipp: Prüfe die Anzahl laufender Jobs via `jobs -p | wc -l` oder nutze `wait -n`).
+# - Wartet am Ende, bis wirklich alle Jobs beendet sind.
+# - Gibt am Ende aus: "THROTTLE_POOL_COMPLETED"
+# ------------------------------------------------------------------------------
+throttle_concurrent_jobs() {
+ local total_jobs="${1:-0}"
+ local max_concurrent="${2:-2}"
+
+ # TODO: Starte total_jobs und drossle Concurrency auf max_concurrent
+ # TODO: Warte auf verbleibende Jobs
+ # TODO: Gib "THROTTLE_POOL_COMPLETED" aus
+ echo "TODO: Implementiere throttle_concurrent_jobs"
+ return 1
+}
diff --git a/courses/bash/09_parallelisierung_mit_xargs_und_parallel/index.html b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/index.html
new file mode 100644
index 0000000..1d14d32
--- /dev/null
+++ b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/index.html
@@ -0,0 +1,133 @@
+
+
+
+
+
+ Bash 09: Parallelisierung mit xargs & parallel
+
+
+
+
+
+
+
+
🚀 Lehrpfad 3: High-Performance & Parallelisierung
+
Bash 09: Parallelisierung mit xargs & parallel
+
Lerne Multiprocessing im Terminal: Beschleunige I/O- und Rechenoperationen mit xargs -P, GNU parallel und nativem Background Worker Management.
+
+
+
+
+
+
+
⚡ 1. Multithreading & Concurrency im Terminal
+
Standard-Shell-Skripte laufen sequentiell in einem einzigen Prozess ab. Auf Multi-Core-CPUs (8, 16 oder 64 Kerne) verschenkt das enorme Rechenkapazität. Durch Parallelisierung teilen wir Arbeitslasten auf:
# 🎯 1. xargs mit Parallelitäts-Flag -P (z.B. 4 parallele Prozesse):
+cat domains.txt | xargs -P 4 -I {} curl -s -I "https://{}"
+
+# 🎯 2. Native Bash Worker mit & und wait:
+for file in *.log; do
+ analyze_log "$file" &
+done
+wait # Blockiert, bis ALLE Subshells im Hintergrund fertig sind
+
+
+
+
+
🏭 2. Didaktische Analogie: Die Supermarkt-Kassen
+
+
🛒 Der Supermarkt-Vergleich
+
Sequentiell (Single-Threaded): 100 Kunden stehen an einer einzigen Kasse an. Kunde 1 zahlt, dann Kunde 2, usw. Die Schlange dauert 100 Minuten.
+
Parallel (xargs -P 4 / Worker Pool): Der Filialleiter öffnet 4 Kassen gleichzeitig. 4 Kunden werden parallel bedient. Die Gesamtdauer sinkt auf 25 Minuten!
+
Throttling: Wenn nicht unendlich Kassenpersonal da ist, begrenzt ein Pool (MAX_WORKERS) die Anzahl geöffneter Schalter, damit der Laden nicht im Chaos versinkt.
+
+
+
+
+
+
📑 3. Cheat-Sheet: Parallelisierungs-Werkzeuge
+
+
+
+
+
Befehl / Syntax
+
Bedeutung
+
Beispiel
+
+
+
+
+
xargs -P <N> -I {}
+
Führt Befehl für jedes Item mit max. N Threads aus
+
cat urls.txt | xargs -P 8 -I {} curl -O {}
+
+
+
command &
+
Startet Befehl/Subshell asynchron im Hintergrund
+
backup_db &
+
+
+
$!
+
PID des zuletzt im Hintergrund gestarteten Prozesses
+
pids+=($!)
+
+
+
wait "${pids[@]}"
+
Wartet auf Beendigung aller gelisteten Prozess-IDs
+
wait "${pids[@]}"
+
+
+
jobs -rp
+
Listet PIDs aller aktuell laufenden Hintergrundjobs
+
[ $(jobs -rp | wc -l) -ge 4 ]
+
+
+
+
+
+
+
+
+
🎯 Aufgaben & Checkliste in aufgabe.sh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💡 DevOps Pro-Tipp: CPU-Kerne dynamisch ermitteln
+
Verwende nproc oder $(grep -c ^processor /proc/cpuinfo), um die Thread-Anzahl automatisch an die Hardware des Servers anzupassen!
+
+
diff --git a/courses/bash/09_parallelisierung_mit_xargs_und_parallel/musterloesung.sh b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/musterloesung.sh
new file mode 100644
index 0000000..b199fea
--- /dev/null
+++ b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/musterloesung.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# Bash 09: Parallelisierung mit xargs, GNU parallel & Background Workers
+# MUSTERLÖSUNG
+# ==============================================================================
+
+parallel_xargs_process() {
+ local input_file="${1:-}"
+ local max_procs="${2:-2}"
+
+ if [ -z "$input_file" ] || [ ! -f "$input_file" ]; then
+ return 1
+ fi
+
+ # Paralleles Ausführen via xargs
+ xargs -P "$max_procs" -I {} bash -c 'echo "PROCESSED: $1"' _ {} < "$input_file"
+}
+
+background_workers_wait() {
+ local tasks=("$@")
+
+ if [ ${#tasks[@]} -eq 0 ]; then
+ return 1
+ fi
+
+ local pids=()
+
+ for task in "${tasks[@]}"; do
+ (
+ echo "WORKER_START: $task"
+ sleep 0.02
+ echo "WORKER_DONE: $task"
+ ) &
+ pids+=($!)
+ done
+
+ # Warten auf alle Hintergrund-Prozesse
+ for pid in "${pids[@]}"; do
+ wait "$pid"
+ done
+
+ echo "ALL_WORKERS_FINISHED"
+ return 0
+}
+
+parallel_batch_calculator() {
+ local numbers_file="${1:-}"
+ local max_procs="${2:-4}"
+
+ if [ -z "$numbers_file" ] || [ ! -f "$numbers_file" ]; then
+ return 1
+ fi
+
+ # Berechne n^2 parallel und formatiere Ausgabe numerisch sortiert
+ xargs -P "$max_procs" -I {} bash -c 'num="$1"; sq=$((num * num)); echo "$num^2 = $sq"' _ {} < "$numbers_file" | sort -n
+}
+
+throttle_concurrent_jobs() {
+ local total_jobs="${1:-0}"
+ local max_concurrent="${2:-2}"
+
+ if [ "$total_jobs" -le 0 ]; then
+ return 1
+ fi
+
+ for ((i=1; i<=total_jobs; i++)); do
+ (
+ echo "JOB_RUNNING: $i"
+ sleep 0.05
+ ) &
+
+ # Sobald mehr oder gleich max_concurrent Jobs laufen: warten bis ein Job fertig ist
+ while [ $(jobs -rp | wc -l) -ge "$max_concurrent" ]; do
+ sleep 0.01
+ done
+ done
+
+ # Auf verbleibende Jobs warten
+ wait
+
+ echo "THROTTLE_POOL_COMPLETED"
+ return 0
+}
diff --git a/courses/bash/09_parallelisierung_mit_xargs_und_parallel/test_aufgabe.sh b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/test_aufgabe.sh
new file mode 100644
index 0000000..eb3d1d5
--- /dev/null
+++ b/courses/bash/09_parallelisierung_mit_xargs_und_parallel/test_aufgabe.sh
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# Testsuite: Bash 09 - Parallelisierung mit xargs & parallel
+# ==============================================================================
+set -e
+
+TARGET_FILE="${1:-aufgabe.sh}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+TARGET_PATH="${SCRIPT_DIR}/${TARGET_FILE}"
+
+if [ ! -f "$TARGET_PATH" ]; then
+ echo "❌ Fehler: Zieldatei '$TARGET_PATH' nicht gefunden!"
+ exit 1
+fi
+
+source "$TARGET_PATH"
+
+passed=0
+total=4
+
+echo "🧪 Starte Tests für Modul 09 (${TARGET_FILE})..."
+echo "---------------------------------------------------------"
+
+# TEST 1: parallel_xargs_process
+TMP_INPUT=$(mktemp)
+echo -e "server01\nserver02\nserver03" > "$TMP_INPUT"
+output1=$(parallel_xargs_process "$TMP_INPUT" 3 || true)
+rm -f "$TMP_INPUT"
+
+if echo "$output1" | grep -q "PROCESSED: server01" && \
+ echo "$output1" | grep -q "PROCESSED: server02" && \
+ echo "$output1" | grep -q "PROCESSED: server03"; then
+ echo "✓ Test 1/4: parallel_xargs_process erfolgreich bestanden."
+ passed=$((passed + 1))
+else
+ echo "✗ Test 1/4 fehlgeschlagen: parallel_xargs_process liefert nicht erwartete Ausgaben. Output: $output1"
+fi
+
+# TEST 2: background_workers_wait
+output2=$(background_workers_wait "alpha" "beta" "gamma" || true)
+if echo "$output2" | grep -q "WORKER_START: alpha" && \
+ echo "$output2" | grep -q "WORKER_DONE: gamma" && \
+ echo "$output2" | grep -q "ALL_WORKERS_FINISHED"; then
+ echo "✓ Test 2/4: background_workers_wait erfolgreich bestanden."
+ passed=$((passed + 1))
+else
+ echo "✗ Test 2/4 fehlgeschlagen: background_workers_wait fehlerhaft. Output: $output2"
+fi
+
+# TEST 3: parallel_batch_calculator
+TMP_NUMS=$(mktemp)
+echo -e "5\n2\n8\n1" > "$TMP_NUMS"
+output3=$(parallel_batch_calculator "$TMP_NUMS" 4 || true)
+rm -f "$TMP_NUMS"
+
+expected3="1^2 = 1
+2^2 = 4
+5^2 = 25
+8^2 = 64"
+
+if [ "$(echo "$output3" | tr -d '\r')" = "$expected3" ]; then
+ echo "✓ Test 3/4: parallel_batch_calculator erfolgreich bestanden."
+ passed=$((passed + 1))
+else
+ echo "✗ Test 3/4 fehlgeschlagen: Erwartete:
+$expected3
+Erhalten:
+$output3"
+fi
+
+# TEST 4: throttle_concurrent_jobs
+output4=$(throttle_concurrent_jobs 4 2 || true)
+if echo "$output4" | grep -q "JOB_RUNNING: 1" && \
+ echo "$output4" | grep -q "JOB_RUNNING: 4" && \
+ echo "$output4" | grep -q "THROTTLE_POOL_COMPLETED"; then
+ echo "✓ Test 4/4: throttle_concurrent_jobs erfolgreich bestanden."
+ passed=$((passed + 1))
+else
+ echo "✗ Test 4/4 fehlgeschlagen: throttle_concurrent_jobs fehlerhaft. Output: $output4"
+fi
+
+echo "---------------------------------------------------------"
+if [ "$passed" -eq "$total" ]; then
+ echo "🎉 Alle $total Tests für Modul 09 erfolgreich bestanden!"
+ exit 0
+else
+ echo "❌ $passed von $total Tests bestanden."
+ exit 1
+fi
diff --git a/courses/bash/10_prozess_substitution_und_coproc/README.md b/courses/bash/10_prozess_substitution_und_coproc/README.md
new file mode 100644
index 0000000..c70ff97
--- /dev/null
+++ b/courses/bash/10_prozess_substitution_und_coproc/README.md
@@ -0,0 +1,87 @@
+# Bash 10: Prozess-Substitution, Named Pipes & Co-Prozesse (coproc) 🔄
+
+Willkommen zu **Modul 10**! In diesem Modul lernst du fortgeschrittene IPC-Mechanismen (Inter-Process Communication) und Stream-Manipulationen kennen, mit denen professionelle DevOps-Ingenieure Datenströme ohne langsame temporäre Festplattendateien verarbeiten.
+
+---
+
+## 💡 1. Das Wichtigste in Kürze
+
+### 1.1 Process Substitution `<(...)` (Befehl als Datei lesen)
+Viele Unix-Befehle (wie `diff`, `comm`, `join`) akzeptieren nur Dateipfade als Argumente, keine Pipes. Mit `<(command)` erstellt die Shell einen virtuellen Datei-Deskriptor (`/dev/fd/63`):
+```bash
+# Vergleicht die Ausgaben zweier Server-Konfigurationen direkt:
+diff -u <(ssh srv1 "cat /etc/nginx/nginx.conf") <(ssh srv2 "cat /etc/nginx/nginx.conf")
+
+# Variablenverlust in while-Schleifen vermeiden:
+count=0
+while read -r line; do
+ ((count++))
+done < <(find . -type f)
+echo "Gefundene Dateien: $count" # Funktioniert! (Keine Subshell)
+```
+
+### 1.2 Process Substitution `>(...)` (In Befehl schreiben)
+Erlaubt das Verzweigen von Streams mit `tee`:
+```bash
+# Schreibt Logs roh in access.log und filtert parallel 500er Fehler in errors.log:
+cat stream.log | tee >(grep "500 Internal Error" > errors.log) > access.log
+```
+
+### 1.3 Named Pipes (`mkfifo`)
+Eine Named Pipe ist eine Datei im Dateisystem, die als bidirektionaler Puffer zwischen zwei unabhängigen Prozessen agiert:
+```bash
+mkfifo /tmp/my_pipe
+# Prozess A (Schreiber):
+echo "Build complete" > /tmp/my_pipe &
+
+# Prozess B (Leser):
+read -r status < /tmp/my_pipe
+echo "Status empfangen: $status"
+rm -f /tmp/my_pipe
+```
+
+### 1.4 Bash Co-Prozesse (`coproc`)
+Ein `coproc` startet ein Programm asynchron im Hintergrund und öffnet zwei Dateideskriptoren für bidirektionale Kommunikation:
+```bash
+coproc WORKER {
+ while read -r input; do
+ [ "$input" = "QUIT" ] && break
+ echo "PROCESSED: $input"
+ done
+}
+
+# In den Worker schreiben:
+echo "payload1" >&"${WORKER[1]}"
+
+# Antwort vom Worker lesen:
+read -r response <&"${WORKER[0]}"
+echo "Antwort: $response"
+
+# Beenden:
+echo "QUIT" >&"${WORKER[1]}"
+```
+
+---
+
+## 🎯 Aufgaben in `aufgabe.sh`
+
+1. **`compare_command_outputs(cmd1, cmd2)`**:
+ Vergleicht zwei Shell-Befehle direkt mit `diff -u <(...) <(...)`. Liefert `"IDENTICAL"` bei Gleichheit.
+2. **`stream_splitter_tee(input_text, raw_file, uppercase_file)`**:
+ Nutzt `tee >(tr '[:lower:]' '[:upper:]' > "$upper")`, um Daten in zwei Streams aufzuspalten.
+3. **`named_pipe_transfer(fifo_path, message)`**:
+ Erstellt eine Named Pipe (`mkfifo`), überträgt eine Nachricht asynchron und liest sie im Hauptprozess.
+4. **`coproc_calculator_interactive(expr1, expr2)`**:
+ Startet einen interaktiven `coproc` Rechen-Daemon und kommuniziert über Dateideskriptoren `${CALC[0]}` und `${CALC[1]}`.
+
+---
+
+## 🧪 Tests ausführen
+
+```bash
+# Eigene Lösung testen:
+bash test_aufgabe.sh
+
+# Musterlösung testen:
+bash test_aufgabe.sh musterloesung.sh
+```
diff --git a/courses/bash/10_prozess_substitution_und_coproc/aufgabe.sh b/courses/bash/10_prozess_substitution_und_coproc/aufgabe.sh
new file mode 100644
index 0000000..07aa664
--- /dev/null
+++ b/courses/bash/10_prozess_substitution_und_coproc/aufgabe.sh
@@ -0,0 +1,120 @@
+#!/usr/bin/env bash
+# ==============================================================================
+# Bash 10: Prozess-Substitution, Named Pipes & Co-Prozesse (coproc)
+# ==============================================================================
+# In diesem Modul lernst du fortschrittliche Linux IPC-Mechanismen:
+# Process Substitution <(...), Stream-Fächerung >(...), Named Pipes (mkfifo)
+# und interaktive Co-Prozesse (coproc).
+# ==============================================================================
+
+# ------------------------------------------------------------------------------
+# TODO 1: Befehlsausgaben ohne temporäre Dateien vergleichen (<(...))
+# ------------------------------------------------------------------------------
+# Implementiere die Funktion `compare_command_outputs`.
+# Parameter:
+# $1: Erster Bash-Befehl als String (z.B. "sort liste1.txt")
+# $2: Zweiter Bash-Befehl als String (z.B. "sort liste2.txt")
+# Verhalten:
+# - Vergleicht die Ausgaben beider Befehle direkt mit `diff -u` und Prozess-Substitution:
+# diff -u <(bash -c "$1") <(bash -c "$2")
+# - Wenn beide Ausgaben identisch sind: Gib "IDENTICAL" aus und return 0.
+# - Wenn Unterschiede existieren: Gib den Diff aus und return 1.
+# ------------------------------------------------------------------------------
+compare_command_outputs() {
+ local cmd1="${1:-}"
+ local cmd2="${2:-}"
+
+ # TODO: Prüfe Parameter
+ # TODO: Führe diff -u mit <(bash -c "$cmd1") und <(bash -c "$cmd2") aus
+ echo "TODO: Implementiere compare_command_outputs"
+ return 1
+}
+
+# ------------------------------------------------------------------------------
+# TODO 2: Stream-Verzweigung mit tee & >(...)
+# ------------------------------------------------------------------------------
+# Implementiere die Funktion `stream_splitter_tee`.
+# Parameter:
+# $1: Eingabetext (z.B. "Fehler: Connection Timeout auf DB-Server")
+# $2: Pfad zur Roh-Logdatei (z.B. "/tmp/raw.log")
+# $3: Pfad zur transformierten Datei (z.B. "/tmp/uppercase.log")
+# Verhalten:
+# - Schreibt den Eingabetext unverändert in $2.
+# - Leitet den Text gleichzeitig über Prozess-Substitution `>(tr '[:lower:]' '[:upper:]' > "$3")`
+# in Großbuchstaben nach $3 um (mit `tee`).
+# - Wartet kurz/synchronisiert, sodass beide Dateien vollständig geschrieben sind.
+# - Return 0 bei Erfolg.
+# ------------------------------------------------------------------------------
+stream_splitter_tee() {
+ local input_text="${1:-}"
+ local raw_file="${2:-}"
+ local upper_file="${3:-}"
+
+ # TODO: Validiere Parameter
+ # TODO: Nutze echo "$input_text" | tee "$raw_file" >(tr '[:lower:]' '[:upper:]' > "$upper_file") > /dev/null
+ echo "TODO: Implementiere stream_splitter_tee"
+ return 1
+}
+
+# ------------------------------------------------------------------------------
+# TODO 3: Interprozesskommunikation über Named Pipe (FIFO)
+# ------------------------------------------------------------------------------
+# Implementiere die Funktion `named_pipe_transfer`.
+# Parameter:
+# $1: Pfad zur Named Pipe (z.B. "/tmp/test_fifo")
+# $2: Zu übertragende Nachricht (z.B. "DEPLOYMENT_TRIGGER_V2")
+# Verhalten:
+# - Erstellt eine Named Pipe mit `mkfifo "$1"`. Falls die Pipe existiert, vorher löschen.
+# - Schreibt im Hintergrund (`&`) die Nachricht $2 in die Pipe.
+# - Liest im Hauptprozess die Nachricht aus der Pipe in eine Variable `received`.
+# - Löscht die Pipe nach erfolgreichem Lesen (`rm -f "$1"`).
+# - Gibt aus: "RECEIVED_FIFO: $received"
+# - Return 0.
+# ------------------------------------------------------------------------------
+named_pipe_transfer() {
+ local fifo_path="${1:-}"
+ local message="${2:-}"
+
+ # TODO: Erstelle FIFO mit mkfifo
+ # TODO: Schreibe im Hintergrund ($message > $fifo_path &)
+ # TODO: Lies synchron (read -r received < $fifo_path)
+ # TODO: Bereinige FIFO und gib "RECEIVED_FIFO: $received" aus
+ echo "TODO: Implementiere named_pipe_transfer"
+ return 1
+}
+
+# ------------------------------------------------------------------------------
+# TODO 4: Interaktiver Co-Prozess (coproc)
+# ------------------------------------------------------------------------------
+# Implementiere die Funktion `coproc_calculator_interactive`.
+# Parameter:
+# $1: Erster mathematischer Ausdruck (z.B. "10 + 25")
+# $2: Zweiter mathematischer Ausdruck (z.B. "100 * 4")
+# Verhalten:
+# - Startet einen Co-Prozess namens CALC mit Bash:
+# coproc CALC {
+# while read -r expr; do
+# [ "$expr" = "EXIT" ] && break
+# echo "$((expr))"
+# done
+# }
+# - Sendet $1 an den Co-Prozess (`${CALC[1]}`), liest das Ergebnis aus (`${CALC[0]}`).
+# - Sendet $2 an den Co-Prozess, liest das zweite Ergebnis aus.
+# - Sendet "EXIT" an den Co-Prozess zum sauberen Beenden.
+# - Gibt die Ergebnisse aus:
+# CALC_RES1:
+# CALC_RES2:
+# - Return 0.
+# ------------------------------------------------------------------------------
+coproc_calculator_interactive() {
+ local expr1="${1:-}"
+ local expr2="${2:-}"
+
+ # TODO: Starte coproc CALC
+ # TODO: Sende expr1, lies res1
+ # TODO: Sende expr2, lies res2
+ # TODO: Sende EXIT
+ # TODO: Gib formatiertes Ergebnis aus
+ echo "TODO: Implementiere coproc_calculator_interactive"
+ return 1
+}
diff --git a/courses/bash/10_prozess_substitution_und_coproc/index.html b/courses/bash/10_prozess_substitution_und_coproc/index.html
new file mode 100644
index 0000000..bad94ec
--- /dev/null
+++ b/courses/bash/10_prozess_substitution_und_coproc/index.html
@@ -0,0 +1,140 @@
+
+
+
+
+
+ Bash 10: Prozess-Substitution & Co-Prozesse
+
+
+
+
+
+
+
+
🚀 Lehrpfad 3: High-Performance & Parallelisierung
+
Bash 10: Prozess-Substitution & Co-Prozesse
+
Nutze Linux-IPC wie ein Meister: Prozess-Substitution <(...), Stream-Fächerung >(...), Named Pipes (mkfifo) und interaktive Co-Prozesse (coproc).
+
+
+
+
+
+
+
🔄 1. Virtuelle Streams mit <(...) und >(...)
+
Prozess-Substitution verwandelt die Ausgabe eines Befehls in einen temporären Dateideskriptor (/dev/fd/XX). Dadurch können Befehle, die Dateien erwarten, direkt mit Streams arbeiten:
📻 Didaktische Analogie: Das Walkie-Talkie für Prozesse
+
Standard-Pipes (|) sind wie eine Einbahnstraße (Wasserrohr). Ein Co-Prozess ist wie ein Walkie-Talkie mit zwei Kanälen: Kanal 1 (${NAME[1]}) zum Sprechen/Senden und Kanal 0 (${NAME[0]}) zum Hören/Empfangen!