Zum Inhalt springen

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.

Zum Kopieren. Die Kopfzeilen sind Pflicht, die drei Abschnitte auch, selbst wenn einer leer bleibt.

template.sh
#!/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 ;;
esac
done
shift $(( 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.

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.

  • 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).
  • then und do auf derselben Zeile wie if und for, 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:
Terminal-Fenster
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.

Ein Kommentar erklärt warum, nicht was. Was der Code tut, steht im Code.

Terminal-Fenster
# 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 Fehler
rsync -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.

Terminal-Fenster
chmod +x skript.sh # ausführbar machen
bash -n skript.sh # Syntax prüfen, ohne auszuführen
shellcheck 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 Fall

ShellCheck 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“
zaehle-zeilen.sh
#!/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=0
verzeichnis="."
# Funktionen
# Gibt die Benutzung auf stderr aus und beendet mit Status 1
usage() {
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 Datei
zaehle_datei() {
local datei="$1"
wc -l < "$datei"
}
# Hauptprogramm
while getopts "hv" opt; do
case "$opt" in
v) verbose=1 ;;
*) usage ;;
esac
done
shift $(( OPTIND - 1 ))
if [[ $# -gt 1 ]]; then
usage
elif [[ $# -eq 1 ]]; then
verzeichnis="$1"
fi
if [[ ! -d "$verzeichnis" ]]; then
echo "Fehler: $verzeichnis ist kein Verzeichnis" >&2
exit 2
fi
summe=0
anzahl=0
# nullglob: ohne Treffer bleibt die Schleife leer statt "*.txt" zu verarbeiten
shopt -s nullglob
for 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:

Terminal-Fenster
chmod +x zaehle-zeilen.sh
shellcheck zaehle-zeilen.sh && echo "ShellCheck ok"
./zaehle-zeilen.sh -v /etc
./zaehle-zeilen.sh /gibt/es/nicht; echo "Exit-Status: $?"