POSIX-Shell-Checkliste fuer portable Produktionsskripte
AI generated
$_
#!/
Bash · POSIX · Checkliste · Portabilitaet
POSIX-Shell-Checkliste
Skripte Schritt fuer Schritt portabel machen

Ein Skript als POSIX konform zu bezeichnen ist schnell behauptet und selten geprueft. Diese Checkliste liefert konkrete Schritte, Werkzeuge und eine Testmatrix, um ein bestehendes Skript systematisch gegen Dash, BusyBox ash und andere schlanke Shells abzusichern.

19 Min. Lesezeit checkbashisms · ShellCheck · dash · ksh · busybox Migration · Testmatrix

1. Warum eine Checkliste statt Gefuehl

Viele Teams behaupten, ihre Skripte seien POSIX konform, weil sie irgendwann einmal unter Dash getestet wurden. Ohne systematische Pruefung ist diese Aussage aber wertlos, denn ein einziger neuer Codeblock mit einem Array oder einem [[ ]] Test reicht, um die POSIX Konformitaet unbemerkt wieder zu brechen. Eine POSIX Checkliste macht aus einem vagen Anspruch einen wiederholbaren, automatisierbaren Pruefprozess, der bei jeder Aenderung neu durchlaufen wird.

Der Wert einer solchen Checkliste liegt nicht in der Theorie des POSIX Standards, sondern in der praktischen Reihenfolge der Schritte: zuerst den Zielrahmen festlegen, dann automatisiert pruefen, dann gezielt umbauen, zuletzt gegen mehrere echte Shells testen. Diese Reihenfolge verhindert, dass Teams Zeit in manuelle Codereviews stecken, die ein Werkzeug in Sekunden zuverlaessiger erledigt. Die folgende Checkliste orientiert sich an genau dieser Reihenfolge und eignet sich sowohl fuer die einmalige Migration bestehender Skripte als auch fuer die laufende Absicherung neuer Skripte in der CI Pipeline.

2. Schritt 1: Shebang und Zielshells festlegen

Der erste Schritt jeder POSIX Checkliste ist eine bewusste Entscheidung, nicht eine technische Pruefung: Welche Shells muss dieses Skript tatsaechlich unterstuetzen? Ein Skript, das ausschliesslich in einem kontrollierten Docker Image mit Bash 5.x laeuft, braucht keine POSIX Konformitaet. Ein Skript, das als Paket Postinst Hook auf beliebigen Debian, Alpine oder RHEL Systemen landen kann, muss dagegen tatsaechlich unter Dash, BusyBox ash und moeglicherweise ksh funktionieren.

Diese Entscheidung wird direkt im Shebang dokumentiert: #!/bin/sh signalisiert den Anspruch auf echte POSIX Konformitaet, #!/usr/bin/env bash signalisiert bewusst Bash Abhaengigkeit. Ein Kommentar direkt unter dem Shebang, der die getesteten Zielshells auflistet, macht die Entscheidung fuer alle nachfolgenden Bearbeiter sichtbar und verhindert, dass jemand versehentlich Bash Syntax in ein vermeintlich portables Skript einbaut, ohne die Konsequenz zu kennen.


#!/bin/sh
# Target shells: dash 0.5.x, busybox ash 1.36, POSIX ksh
# Tested with: dash, busybox sh, mksh — see CI matrix in step 6
set -eu

# From here on: only POSIX-defined syntax is allowed in this file

3. Schritt 2: Statische Analyse mit ShellCheck und checkbashisms

Sobald der Zielrahmen feststeht, folgt die automatisierte Pruefung. shellcheck --shell=sh skript.sh meldet jede Stelle, die nicht dem POSIX Standard entspricht, mit Erklaerung und Zeilennummer. checkbashisms aus dem Debian Paket devscripts ergaenzt diese Pruefung um Muster, die speziell aus der Debian Migration von Bash zu Dash bekannt sind. Beide Werkzeuge zusammen decken in der Praxis fast alle relevanten POSIX Verstoesse ab, bevor ein Mensch das Skript ueberhaupt liest.

Wichtig fuer die Checkliste: Diese Pruefung muss vor jeder manuellen Anpassung stehen, nicht danach. Wer zuerst manuell umbaut und erst anschliessend prueft, uebersieht erfahrungsgemaess Stellen, die auf den ersten Blick harmlos wirken, etwa ein einzelnes echo mit Escape Sequenz oder ein local mit Befehlssubstitution in derselben Zeile. Die Werkzeuge finden diese Stellen zuverlaessig und liefern gleichzeitig die Zeilennummer fuer den naechsten Schritt.


#!/usr/bin/env bash
set -euo pipefail

echo "=== Step 2: static POSIX analysis ==="

# ShellCheck restricted to POSIX sh rules
shellcheck --shell=sh --severity=warning deploy.sh

# Debian's dedicated bashism scanner
checkbashisms --posix deploy.sh

# Exit non-zero if either tool reported anything, for use as a CI gate

4. Schritt 3: Arrays und [[ ]] konsequent entfernen

Der dritte Schritt der Checkliste ist die gezielte Beseitigung der beiden haeufigsten POSIX Verstoesse: Arrays und die erweiterte Testklammer [[ ]]. Arrays werden durch durch Leerzeichen oder ein anderes Trennzeichen getrennte Strings ersetzt, kombiniert mit set -- zum Aufteilen in Positionsparameter, oder durch mehrere klar benannte Einzelvariablen, wenn die Anzahl der Elemente von vornherein feststeht. [[ ]] Mustervergleiche werden durch case Anweisungen ersetzt, die in POSIX Shells identisch funktionieren und sogar oft lesbarer sind als komplexe Regex Ausdruecke.

Diese Umbauten sind der arbeitsintensivste Teil der Checkliste, weil sie nicht mechanisch automatisierbar sind, sondern Verstaendnis der urspruenglichen Logik erfordern. Ein guter Zwischenschritt ist, jede gefundene Stelle einzeln zu kommentieren, warum die POSIX Alternative dasselbe Verhalten liefert, bevor der Code gemergt wird. Das erleichtert spaeter auch Reviewern, die die POSIX Einschraenkung selbst nicht kennen, die Aenderung nachzuvollziehen.


#!/bin/sh
set -eu

# BEFORE (bash-only, fails the checklist)
# declare -a hosts=(web1 web2 web3)
# for h in "${hosts[@]}"; do ping -c1 "$h"; done

# AFTER (POSIX-safe, passes the checklist)
hosts="web1 web2 web3"
for h in $hosts; do
  ping -c 1 "$h"
done

# BEFORE (bash-only [[ ]] pattern match)
# if [[ "$env" == prod* ]]; then echo "production"; fi

# AFTER (POSIX case statement)
case "$env" in
  prod*) echo "production" ;;
esac

5. Schritt 4: echo durch printf ersetzen

Der vierte Punkt der Checkliste betrifft ein oft uebersehenes Detail: echo verhaelt sich zwischen POSIX Shells nicht identisch, insbesondere bei Escape Sequenzen und beim Flag -n zum Unterdruecken des Zeilenumbruchs. POSIX definiert das Verhalten von echo bewusst als implementierungsabhaengig, was in der Praxis bedeutet, dass jede Shell eigene Regeln anwenden darf. printf dagegen ist in POSIX vollstaendig und eindeutig spezifiziert und verhaelt sich in Dash, BusyBox ash, ksh und Bash identisch.

Der Umbau ist mechanisch einfach: printf '%s\n' "$var" ersetzt echo "$var" in fast allen Faellen direkt. Fuer formatierte Ausgaben mit mehreren Variablen liefert printf zusaetzlich echte Formatstrings, die lesbarer sind als aneinandergehaengte echo Aufrufe. Diese Umstellung sollte in der Checkliste als eigener, kleiner Schritt gefuehrt werden, weil sie leicht zu automatisieren ist und in Codereviews kaum Diskussion braucht.

6. Schritt 5: local, Funktionen und Namensraeume pruefen

Funktionsdefinitionen muessen durchgaengig der POSIX Form name() { ... } folgen, das function Schluesselwort ist zu entfernen. Bei local, das streng genommen keine POSIX Funktion ist, aber von praktisch allen relevanten Shells als Erweiterung unterstuetzt wird, prueft die Checkliste, dass Deklaration und Zuweisung mit Befehlssubstitution niemals in derselben Zeile stehen, um den Exit Code nicht zu verlieren. Diese Regel gilt in Dash noch strenger als in Bash und ist deshalb ein haeufiger Stolperstein bei der Migration.

Zusaetzlich sollte die Checkliste pruefen, ob globale Variablen versehentlich durch fehlendes local ueberschrieben werden, ein Fehler, der in laengeren Skripten mit vielen Funktionen besonders leicht uebersehen wird. Ein einfacher Test: Jede Funktion sollte mit denselben Eingaben zweimal hintereinander aufgerufen werden und identische Ergebnisse liefern. Weicht das Ergebnis beim zweiten Aufruf ab, deutet das auf einen fehlenden local Scope oder einen versehentlichen globalen Seiteneffekt hin.

7. Schritt 6: Testmatrix aus mehreren Shells aufbauen

Statische Analyse allein reicht nicht aus, um POSIX Konformitaet zu garantieren, weil manche Unterschiede sich erst zur Laufzeit zeigen. Die Checkliste verlangt deshalb eine echte Testmatrix, die das Skript unter mindestens drei unterschiedlichen Shell Implementierungen ausfuehrt: Dash als Referenz fuer Debian und Ubuntu, BusyBox ash als Referenz fuer Alpine und minimale Container, sowie mksh oder ein anderer ksh Ableger als dritte, unabhaengige Implementierung, die Fehler aufdeckt, die zwischen den ersten beiden zufaellig identisch ausfallen.

In der Praxis genuegt ein einfaches Bash Skript, das dasselbe Zielskript nacheinander mit dash, busybox sh und mksh aufruft und den Exit Code sowie die Ausgabe vergleicht. Unterschiede in der Ausgabe zwischen den drei Shells sind ein zuverlaessiges Signal fuer verbliebene, nicht POSIX konforme Konstrukte, die weder ShellCheck noch checkbashisms erkannt haben. Diese Testmatrix sollte fester Bestandteil der CI Pipeline sein, nicht nur ein einmaliger manueller Test vor der Migration.


#!/usr/bin/env bash
set -euo pipefail

TARGET="./deploy.sh"
SHELLS=(dash busybox mksh)

for shell in "${SHELLS[@]}"; do
  if ! command -v "$shell" >/dev/null 2>&1; then
    echo "[SKIP] $shell not installed"
    continue
  fi
  echo "=== Testing under: $shell ==="
  if [ "$shell" = "busybox" ]; then
    busybox sh "$TARGET" && echo "[OK] busybox sh" || echo "[FAIL] busybox sh"
  else
    "$shell" "$TARGET" && echo "[OK] $shell" || echo "[FAIL] $shell"
  fi
done

8. Schritt 7: CI-Gate fuer neue Skripte einrichten

Der letzte Schritt der Checkliste sichert das erreichte Ergebnis dauerhaft ab: Jeder neue Commit, der ein Skript mit #!/bin/sh Shebang aendert oder hinzufuegt, durchlaeuft automatisch ShellCheck, checkbashisms und die Testmatrix aus Schritt 6, bevor er gemergt werden darf. Ohne dieses Gate verfaellt jede POSIX Migration innerhalb weniger Monate, weil neue Entwickler die urspruengliche Entscheidung nicht kennen und versehentlich Bash Syntax einbauen.

Ein pragmatischer Ansatz ist ein einzelnes Makefile Target oder ein Pre Commit Hook, der alle Dateien mit #!/bin/sh Shebang automatisch findet und die Checkliste darauf anwendet, statt sich auf die Erinnerung einzelner Entwickler zu verlassen. Dieses Gate ist der Unterschied zwischen einer einmaligen Migration, die schleichend wieder zerfaellt, und einer dauerhaft eingehaltenen POSIX Checkliste.

9. Checkliste im Ueberblick

Die folgende Tabelle fasst alle sieben Schritte kompakt zusammen, inklusive des jeweiligen Werkzeugs und des erwarteten Ergebnisses.

Schritt Aktion Werkzeug Ergebnis
1 Zielshells festlegen Shebang, Kommentar Dokumentierte Entscheidung
2 Statische Analyse ShellCheck, checkbashisms Liste der Verstoesse
3 Arrays, [[ ]] entfernen manueller Umbau POSIX Syntax
4 echo durch printf ersetzen Suchen und Ersetzen Einheitliche Ausgabe
5 bis 7 Funktionen, Testmatrix, CI-Gate dash, busybox, mksh, CI Dauerhafte POSIX Konformitaet

Der Ueberblick zeigt: Eine POSIX Checkliste ist kein einmaliges Audit, sondern ein wiederholbarer Prozess, der von der bewussten Entscheidung bis zum automatisierten CI Gate reicht. Wer alle sieben Schritte durchlaeuft, hat nicht nur ein aktuell portables Skript, sondern einen Mechanismus, der es auch bei zukuenftigen Aenderungen portabel haelt.

Mironsoft

Shell-Automatisierung, DevOps-Tooling und portable Infrastruktur-Skripte

Skripte, die die eigene POSIX Checkliste tatsaechlich bestehen?

Wir bauen die komplette Checkliste als CI Gate, migrieren bestehende Skripte auf echte POSIX Konformitaet und richten die Testmatrix aus Dash, BusyBox und ksh ein.

POSIX-Audit

ShellCheck und checkbashisms ueber den gesamten Skript-Bestand laufen lassen

Migration

Arrays, [[ ]] und Bashismen gezielt durch POSIX Alternativen ersetzen

CI-Gate

Testmatrix aus mehreren Shells dauerhaft in die Pipeline integrieren

10. Zusammenfassung

Eine POSIX Checkliste verwandelt eine vage Behauptung ueber Portabilitaet in einen konkreten, wiederholbaren Prozess. Die sieben Schritte, von der bewussten Zielshell Entscheidung ueber statische Analyse mit ShellCheck und checkbashisms bis zur echten Testmatrix aus Dash, BusyBox ash und mksh, decken systematisch genau die Stellen ab, an denen Skripte typischerweise die POSIX Konformitaet verlieren. Arrays, die erweiterte Testklammer und inkonsistentes echo Verhalten sind dabei die drei haeufigsten Ursachen.

Der wichtigste Teil der Checkliste ist jedoch nicht die einmalige Migration, sondern das abschliessende CI Gate. Ohne automatische Durchsetzung verfaellt jede POSIX Konformitaet innerhalb weniger Monate, weil neue Aenderungen die urspruengliche Entscheidung nicht kennen. Wer die Checkliste konsequent als Pipeline Schritt etabliert, haelt Skripte dauerhaft portabel, statt sie nur einmalig zu bereinigen.

POSIX-Checkliste: das Wichtigste auf einen Blick

Zielrahmen

Vor jeder Migration festlegen, welche Shells das Skript tatsaechlich unterstuetzen muss, und das im Shebang dokumentieren.

Automatisierte Pruefung

shellcheck --shell=sh und checkbashisms finden die meisten Verstoesse, bevor ein Mensch das Skript liest.

Testmatrix

Dash, BusyBox ash und mksh gemeinsam testen, um Laufzeitunterschiede zu finden, die Analyse Tools uebersehen.

CI-Gate

Die komplette Checkliste automatisch bei jedem Commit ausfuehren, sonst zerfaellt die POSIX Konformitaet schleichend.

11. FAQ: POSIX-Shell-Checkliste

1Was ist der erste Schritt der Checkliste?
Die bewusste Entscheidung, welche Shells unterstuetzt werden muessen, dokumentiert im Shebang und einem Kommentar.
2Reicht ShellCheck allein?
Nicht allein, kombiniert mit checkbashisms deckt es fast alle relevanten POSIX Verstoesse ab.
3Warum reicht statische Analyse nicht?
Manche Unterschiede zeigen sich erst zur Laufzeit, deshalb braucht es zusaetzlich eine echte Testmatrix.
4Welche Shells gehoeren in die Testmatrix?
Dash, BusyBox ash und mksh als drei unabhaengige Implementierungen.
5Wie ersetze ich Arrays?
Durch getrennte Strings mit set -- oder klar benannte Einzelvariablen.
6Warum echo durch printf ersetzen?
echo ist implementierungsabhaengig, printf ist in POSIX eindeutig definiert und ueberall identisch.
7Wie ersetze ich [[ ]] Mustervergleiche?
Mit einer case Anweisung, funktioniert identisch in allen POSIX Shells.
8Was mache ich mit local?
Deklaration und Zuweisung mit Befehlssubstitution immer getrennt schreiben.
9Wie bleibt die Konformitaet dauerhaft erhalten?
Mit einem CI Gate, das die Checkliste bei jedem Commit automatisch ausfuehrt.
10Lohnt sich die Checkliste ueberhaupt?
Fuer Systemskripte und Paketskripte ja, fuer Skripte in kontrollierten Docker Images meist nicht noetig.