Projekt 1: Backup mit Rotation
Ein Skript, das einen Ordner packt, mit Zeitstempel benennt und nur die letzten n Sicherungen behält:
set -euo pipefail
backup() {
local quelle=$1 ziel=$2 behalten=${3:-3}
mkdir -p "$ziel"
local name
name="backup-$(basename "$quelle")-$4.tar.gz"
tar -czf "$ziel/$name" -C "$(dirname "$quelle")" "$(basename "$quelle")"
# alte Sicherungen löschen: nach Namen sortieren, nur die neuesten $behalten behalten
ls -1 "$ziel" | sort | head -n -"$behalten" | while read -r alt; do
rm -- "$ziel/$alt"
echo "gelöscht: $alt"
done
echo "erstellt: $name"
}
arbeit=$(mktemp -d)
mkdir "$arbeit/daten"
echo "wichtig" > "$arbeit/daten/a.txt"
for tag in 01 02 03 04 05; do
backup "$arbeit/daten" "$arbeit/sicherungen" 3 "2025-03-$tag"
done
echo "Übrig:"
ls "$arbeit/sicherungen"erstellt: backup-daten-2025-03-01.tar.gz erstellt: backup-daten-2025-03-02.tar.gz erstellt: backup-daten-2025-03-03.tar.gz gelöscht: backup-daten-2025-03-01.tar.gz erstellt: backup-daten-2025-03-04.tar.gz gelöscht: backup-daten-2025-03-02.tar.gz erstellt: backup-daten-2025-03-05.tar.gz Übrig: backup-daten-2025-03-03.tar.gz backup-daten-2025-03-04.tar.gz backup-daten-2025-03-05.tar.gz
Der Zeitstempel kommt hier als Argument (in der Praxis $(date +%F)), damit die Ausgabe reproduzierbar bleibt.
Projekt 2: Logdatei-Report
cat > access.log <<'EOF'
10.0.0.1 - - [01/Mar/2025:10:00:01] "GET /index.html HTTP/1.1" 200 512
10.0.0.2 - - [01/Mar/2025:10:00:05] "GET /api/users HTTP/1.1" 200 1024
10.0.0.1 - - [01/Mar/2025:10:01:10] "POST /api/login HTTP/1.1" 401 64
10.0.0.3 - - [01/Mar/2025:10:02:00] "GET /missing HTTP/1.1" 404 128
10.0.0.1 - - [01/Mar/2025:10:03:44] "GET /index.html HTTP/1.1" 200 512
10.0.0.2 - - [01/Mar/2025:10:04:00] "GET /api/users HTTP/1.1" 500 32
EOF
echo "== Anfragen gesamt: $(wc -l < access.log)"
echo "== Häufigste IPs"
awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -3
echo "== Statuscodes"
awk '{ print $9 }' access.log | sort | uniq -c | sort -k2
echo "== Fehlerquote (4xx/5xx)"
awk '$9 >= 400 { f++ } END { printf "%d von %d (%.1f%%)\n", f, NR, f / NR * 100 }' access.log
echo "== Übertragene Bytes"
awk '{ summe += $10 } END { print summe " Bytes" }' access.log
echo "== Beliebteste Pfade"
awk '{ print $7 }' access.log | sort | uniq -c | sort -rn | head -2== Anfragen gesamt: 6
== Häufigste IPs
3 10.0.0.1
2 10.0.0.2
1 10.0.0.3
== Statuscodes
1 1024
1 128
1 32
2 512
1 64
== Fehlerquote (4xx/5xx)
3 von 6 (50.0%)
== Übertragene Bytes
0 Bytes
== Beliebteste Pfade
6 HTTP/1.1"Projekt 3: Stapelumbenennung
cd "$(mktemp -d)"
touch "Urlaub 1.JPG" "Urlaub 2.JPG" "bild 3.jpg" notiz.txt
i=1
for datei in *.[jJ][pP][gG]; do
neu=$(printf "foto-%03d.jpg" "$i")
mv -- "$datei" "$neu"
echo "$datei -> $neu"
((i++))
done
lsUrlaub 1.JPG -> foto-001.jpg Urlaub 2.JPG -> foto-002.jpg bild 3.jpg -> foto-003.jpg foto-001.jpg foto-002.jpg foto-003.jpg notiz.txt
Projekt 4: Menü und Eingaben
menue() {
local wahl=$1
case "$wahl" in
1) echo "Datum anzeigen (hier nicht ausgeführt)" ;;
2) echo "Dateien zählen: $(ls -1 / | wc -l | awk '{ print ($1 > 0) ? "mehr als 0" : "0" }')" ;;
q) echo "Auf Wiedersehen" ;;
*) echo "Ungültige Eingabe: $wahl" ;;
esac
}
for eingabe in 1 2 x q; do menue "$eingabe"; done
# Interaktive Variante:
# while true; do read -rp "Wahl (1/2/q): " w; menue "$w"; [[ $w == q ]] && break; doneDatum anzeigen (hier nicht ausgeführt) Dateien zählen: mehr als 0 Ungültige Eingabe: x Auf Wiedersehen
Projekt 5: Zahlenraten (nicht interaktiv simuliert)
ziel=42
raten() {
local zahl=$1
if (( zahl < ziel )); then echo "$zahl: zu klein"
elif (( zahl > ziel )); then echo "$zahl: zu groß"
else echo "$zahl: richtig!"; return 0
fi
return 1
}
versuche=0
for tipp in 50 25 37 43 42; do
((versuche++))
raten "$tipp" && break
done
echo "Geschafft in $versuche Versuchen"50: zu groß 25: zu klein 37: zu klein 43: zu groß 42: richtig! Geschafft in 5 Versuchen
Für das echte Spiel: ziel=$((RANDOM % 100 + 1)) und read -rp "Tipp: " tipp in einer while-Schleife.
Projekt 6: Eigenes Kommandozeilenwerkzeug
Ein kleines todo-Programm, das Aufgaben in einer Textdatei verwaltet:
datei=$(mktemp)
todo() {
local befehl=${1:-hilfe}; shift || true
case "$befehl" in
add) echo "$*" >> "$datei" ;;
list) nl -w2 -s'. ' "$datei" ;;
done) sed -i "${1}d" "$datei" ;;
*) echo "Aufruf: todo add TEXT | list | done NUMMER" ;;
esac
}
todo add "Milch kaufen"
todo add "Bash lernen"
todo add "Sport"
todo list
todo done 2
echo "--"
todo list
todo
rm -f "$datei"1. Milch kaufen 2. Bash lernen 3. Sport -- 1. Milch kaufen 2. Sport Aufruf: todo add TEXT | list | done NUMMER
Merke
- Aus kleinen Bausteinen (tar, find, awk, sort, sed) entstehen schnell nützliche Werkzeuge
- Funktionen, Argumente und Strict Mode machen Skripte wiederverwendbar
- Zeitstempel und Zufall der Reproduzierbarkeit wegen als Parameter übergeben
- Ab etwa 100 Zeilen oder komplexer Logik lohnt Python
Aufgabe
Schreibe ein Skript, das alle .log-Dateien eines Ordners älter als 7 Tage komprimiert (find -mtime +7).