Skript-Konvention
Jedes Skript, das du in diesem Kurs abgibst, folgt derselben Konvention. Nicht, weil es nur eine richtige gibt, sondern weil ein Team eine braucht. Wer ein fremdes Skript öffnet, soll in fünf Sekunden wissen, was es tut, wer es geschrieben hat und wo das Hauptprogramm beginnt.
Das Template
Abschnitt betitelt „Das Template“Zum Kopieren. Die Kopfzeilen sind Pflicht, die drei Abschnitte auch, selbst wenn einer leer bleibt.
#!/bin/bash## template.sh# Kurze Beschreibung, was das Skript tut. Gerne zwei Zeilen,# aber nicht mehr. Details gehören in die Funktionen.## 2026-11-05 / Vorname Nachname## Version: 0.1# Benutzung: ./template.sh [-h] [-v] datei
set -euo pipefail
# Konstanten und Variablen
readonly SKRIPTNAME="${0##*/}"readonly LOGDATEI="/tmp/${SKRIPTNAME%.sh}.log"verbose=0
# Funktionen
usage() { echo "Benutzung: $SKRIPTNAME [-h] [-v] datei" >&2 exit 1}
log() { local meldung="$1" printf '%s %s\n' "$(date +%H:%M:%S)" "$meldung" >> "$LOGDATEI" (( verbose )) && echo "$meldung" return 0}
# Hauptprogramm
while getopts "hv" opt; do case "$opt" in v) verbose=1 ;; *) usage ;; esacdoneshift $(( OPTIND - 1 ))[[ $# -eq 1 ]] || usage
log "Start mit Datei $1"Die Kopfzeilen im Detail:
| Zeile | Regel |
|---|---|
| Shebang | #!/bin/bash (fester Pfad, auf Ubuntu immer vorhanden) oder #!/usr/bin/env bash (findet Bash über den PATH, portabel). Beides ist im Kurs erlaubt, aber pro Skript nur eine Zeile 1 |
| Name | Der Dateiname in der ersten Kommentarzeile nach dem Shebang, damit er auch im Ausdruck oder in einem Diff sichtbar ist |
| Zweck | Ein bis zwei Sätze, um zwei Leerzeichen eingerückt. Was, nicht wie |
| Datum / Autor | Eine Zeile: Datum im ISO-Format JJJJ-MM-TT, Schrägstrich, Vor- und Nachname. Bei Teamarbeit alle Namen |
| Version | Optional. 0.1 beim ersten Entwurf, 1.0 wenn es tut, was es soll |
| Benutzung | Optional bei kleinen Skripten. Die Aufrufsyntax, dieselbe wie in usage() |
set -euo pipefail |
Direkt nach dem Kopf, vor dem ersten Befehl. Siehe Bash-Spickzettel |
Umlaute im Kopf sind erlaubt, die Dateien sind UTF-8. In Dateinamen und Variablennamen aber nicht.
Namensregeln
Abschnitt betitelt „Namensregeln“| Was | Regel | Beispiel |
|---|---|---|
| Skriptdatei | Kleinbuchstaben, Bindestrich oder Unterstrich, Endung .sh |
backup-home.sh, addscript.sh |
| Variablen | Kleinbuchstaben mit Unterstrich | anzahl_dateien, zielverzeichnis |
| Konstanten | Grossbuchstaben mit Unterstrich, mit readonly |
readonly MAX_VERSUCHE=3 |
| Umgebungsvariablen | Grossbuchstaben, aber nicht selbst erfinden, wenn es nicht nötig ist | $HOME, $PATH |
| Funktionen | verb_objekt, Kleinbuchstaben |
pruefe_argumente, erstelle_backup, zeige_menue |
| Temporäre Dateien | Mit mktemp erzeugen, nie Namen raten |
tmp=$(mktemp) |
Ein Name sagt, was drin ist oder was passiert. x, tmp2, foo sagen nichts. Dafür nicht übertreiben: i als Schleifenzähler ist in Ordnung.
Einrückung und Layout
Abschnitt betitelt „Einrückung und Layout“- Vier Leerzeichen pro Ebene oder ein Tab. Eins davon wählen und im ganzen Skript dabei bleiben. Das Template nutzt vier Leerzeichen; in vim:
set tabstop=4 shiftwidth=4 expandtab(siehe vim-Spickzettel). thenunddoauf derselben Zeile wieifundfor, getrennt durch Semikolon.- Eine Leerzeile zwischen Funktionen, eine vor jedem Abschnittskommentar.
- Zeilen nicht länger als etwa 100 Zeichen. Lange Pipelines mit
\am Zeilenende umbrechen, die Fortsetzung einrücken:
find "$quelle" -type f -name '*.log' -mtime +30 \ -exec gzip {} \;- Variablen immer in Anführungszeichen:
"$var","$(befehl)","${arr[@]}". $(...)statt Backticks,[[ ]]statt[ ],(( ))für Rechnen.- Funktionen oben, Hauptprogramm unten. Bash liest von oben nach unten, eine Funktion muss definiert sein, bevor sie aufgerufen wird.
Kommentare
Abschnitt betitelt „Kommentare“Ein Kommentar erklärt warum, nicht was. Was der Code tut, steht im Code.
# Schlecht: wiederholt den Code# Zähler um eins erhöhen(( zaehler++ ))
# Gut: erklärt die Absicht# rsync liefert 24 bei "Datei während der Übertragung verschwunden",# das ist bei laufenden Logs normal und kein Fehlerrsync -a "$quelle" "$ziel" || [[ $? -eq 24 ]]Jede Funktion bekommt eine Kommentarzeile darüber: was sie tut, welche Parameter sie erwartet, was sie zurückgibt oder ausgibt. Auskommentierter Code wird gelöscht, nicht aufbewahrt. Dafür gibt es Git.
Vor der Abgabe
Abschnitt betitelt „Vor der Abgabe“chmod +x skript.sh # ausführbar machenbash -n skript.sh # Syntax prüfen, ohne auszuführenshellcheck skript.sh # Stil und Fehler, muss ohne Meldung durchlaufen./skript.sh # einmal ohne Argumente: zeigt usage?./skript.sh -h # Hilfe?./skript.sh test.txt # normaler FallShellCheck ist in der VM mit sudo apt install shellcheck installiert. Im Repository läuft es automatisch bei jedem Merge Request; eine Abgabe mit ShellCheck-Warnungen gilt als nicht fertig. Die häufigsten Codes und ihre Bedeutung stehen im Bash-Spickzettel.
Beispiel: ein vollständiges Skript nach Konvention
Abschnitt betitelt „Beispiel: ein vollständiges Skript nach Konvention“#!/bin/bash## zaehle-zeilen.sh# Zählt die Zeilen aller Textdateien in einem Verzeichnis und gibt# die Summe aus. Mit -v wird jede Datei einzeln aufgeführt.## 2026-11-05 / Vorname Nachname## Version: 1.0# Benutzung: ./zaehle-zeilen.sh [-h] [-v] [verzeichnis]
set -euo pipefail
# Konstanten und Variablen
readonly SKRIPTNAME="${0##*/}"readonly ENDUNG="txt"verbose=0verzeichnis="."
# Funktionen
# Gibt die Benutzung auf stderr aus und beendet mit Status 1usage() { echo "Benutzung: $SKRIPTNAME [-h] [-v] [verzeichnis]" >&2 echo " -v jede Datei einzeln auflisten" >&2 exit 1}
# Gibt die Anzahl Zeilen einer Datei aus# $1: Pfad zur Dateizaehle_datei() { local datei="$1" wc -l < "$datei"}
# Hauptprogramm
while getopts "hv" opt; do case "$opt" in v) verbose=1 ;; *) usage ;; esacdoneshift $(( OPTIND - 1 ))
if [[ $# -gt 1 ]]; then usageelif [[ $# -eq 1 ]]; then verzeichnis="$1"fi
if [[ ! -d "$verzeichnis" ]]; then echo "Fehler: $verzeichnis ist kein Verzeichnis" >&2 exit 2fi
summe=0anzahl=0# nullglob: ohne Treffer bleibt die Schleife leer statt "*.txt" zu verarbeitenshopt -s nullglobfor datei in "$verzeichnis"/*."$ENDUNG"; do zeilen=$(zaehle_datei "$datei") # Kein (( summe += zeilen )): das liefert bei Ergebnis 0 den Exit-Status 1, # und set -e würde das Skript bei einer leeren Datei beenden summe=$(( summe + zeilen )) anzahl=$(( anzahl + 1 )) (( verbose )) && printf '%6d %s\n' "$zeilen" "$datei"done
printf '%d Dateien, %d Zeilen\n' "$anzahl" "$summe"Probelauf:
chmod +x zaehle-zeilen.shshellcheck zaehle-zeilen.sh && echo "ShellCheck ok"./zaehle-zeilen.sh -v /etc./zaehle-zeilen.sh /gibt/es/nicht; echo "Exit-Status: $?"