Sections und Key-Value-Paare ohne externe Tools auslesen
Viele Legacy-Tools und einfache Konfigurationsdateien nutzen bis heute das INI-Format: Sections in eckigen Klammern, darunter Key-Value-Paare. Bash bringt genug Bordmittel mit, um ein solches Format zuverlässig einzulesen, solange man Kommentare, Whitespace und Quoting-Fallstricke von Anfang an einplant statt nachträglich zu flicken.
Inhaltsverzeichnis
- 1. Warum INI-Dateien in Bash-Skripten immer noch vorkommen
- 2. Aufbau einer INI-Datei und die eigentlichen Parsing-Herausforderungen
- 3. Line-by-Line-Parsing mit while read und Regex
- 4. Sections als assoziative Arrays abbilden
- 5. Kommentare, leere Zeilen und Whitespace robust behandeln
- 6. Werte mit Anführungszeichen und Sonderzeichen: die eigentliche Quoting-Falle
- 7. Grenzen eines selbstgebauten Parsers gegenüber echten INI-Parsern
- 8. Wann sich ein externes Tool trotzdem lohnt
- 9. Vollständiges Beispiel und Vergleich der Ansätze
- 10. Zusammenfassung
- 11. FAQ
1. Warum INI-Dateien in Bash-Skripten immer noch vorkommen
Das INI-Format ist alles andere als modern, taucht aber bis heute in Legacy-Tools, PHP-Konfigurationen, systemweiten Diensten und selbstgeschriebenen internen Tools auf, weil es für Menschen extrem leicht zu lesen und zu schreiben ist. Wo YAML oder JSON eine strikte Struktur mit Einrückung oder Klammern verlangen, kommt eine INI-Datei mit einer flachen Liste aus Sections und einfachen schlüssel=wert-Zeilen aus, ohne dass Nutzer sich um Escaping oder Verschachtelung kümmern müssen.
Wenn ein Bash-Skript eine solche Konfigurationsdatei einlesen muss, etwa um Datenbank-Zugangsdaten aus einer my.cnf-ähnlichen Datei zu holen oder ein internes Deployment-Tool zu konfigurieren, ist eine externe Abhängigkeit wie Python oder ein dediziertes INI-Parsing-Tool nicht immer verfügbar oder erwünscht, insbesondere in schlanken Container-Images oder auf restriktiv verwalteten Servern. Ein selbstgebauter Parser mit reinen Bash-Bordmitteln schließt diese Lücke, solange die Anforderungen überschaubar bleiben.
2. Aufbau einer INI-Datei und die eigentlichen Parsing-Herausforderungen
Eine typische INI-Datei besteht aus optionalen Sections in eckigen Klammern wie [database], gefolgt von Zeilen der Form schlüssel = wert, dazu Kommentarzeilen, die meist mit ; oder # beginnen, und leeren Zeilen zur Gliederung. Werte vor der ersten Section gehören traditionell zu einer impliziten, namenlosen Standard-Section.
Die eigentliche Schwierigkeit beim Parsen liegt nicht in diesem Grundgerüst, sondern in den Details: Werte können in Anführungszeichen stehen oder nicht, Leerzeichen um das Gleichheitszeichen sind mal vorhanden und mal nicht, Kommentare können am Zeilenende hinter einem Wert stehen, und manche INI-Dialekte erlauben sogar Kommentarzeichen innerhalb von Werten, solange diese in Anführungszeichen stehen. Ein robuster Parser muss diese Fälle bewusst entscheiden, statt sie stillschweigend falsch zu behandeln.
3. Line-by-Line-Parsing mit while read und Regex
Der Ausgangspunkt für einen Bash-INI-Parser ist eine while read-Schleife, die die Datei zeilenweise einliest, kombiniert mit Bashs eingebautem Regex-Matching über [[ $line =~ regex ]]. Eine Section-Zeile lässt sich mit einem einfachen Muster wie ^\[([^]]+)\]$ erkennen, eine Key-Value-Zeile mit ^([^=]+)=(.*)$, wobei die Übereinstimmungen anschließend über das Array BASH_REMATCH abgerufen werden.
Wichtig ist, die Schleife mit while IFS= read -r line statt einfachem while read line zu schreiben: IFS= verhindert, dass führende und nachfolgende Leerzeichen der Zeile verschluckt werden, und -r verhindert, dass Backslashes in Werten fälschlich als Escape-Zeichen interpretiert werden. Beide Details werden in Tutorials häufig weggelassen, führen aber bei realen Konfigurationsdateien schnell zu falsch geparsten Werten.
#!/usr/bin/env bash
set -euo pipefail
parse_ini_naive() {
local file="$1" section=""
while IFS= read -r line; do
# Strip comments and surrounding whitespace first
line="${line%%[#;]*}"
line="$(echo "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
[[ -z "$line" ]] && continue
if [[ "$line" =~ ^\[([^]]+)\]$ ]]; then
section="${BASH_REMATCH[1]}"
elif [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then
local key="${BASH_REMATCH[1]%% }" value="${BASH_REMATCH[2]}"
echo "[$section] $key = $value"
fi
done < "$file"
}
4. Sections als assoziative Arrays abbilden
Reine Textausgabe reicht für ein Debugging-Skript, aber ein Konfigurationsparser soll die Werte typischerweise programmatisch abrufbar machen. Ab Bash 4 bieten sich dafür assoziative Arrays an, deklariert mit declare -A. Der gängige Trick, um zweistufige Struktur (Section plus Schlüssel) in einem einzigen, flachen assoziativen Array abzubilden, ist ein zusammengesetzter Schlüssel wie "$section.$key".
Diese Struktur ist zwar weniger elegant als ein natives verschachteltes Array, das Bash nicht kennt, funktioniert aber zuverlässig und lässt sich leicht durchsuchen, etwa mit ${!ini_data[@]}, um alle Schlüssel einer bestimmten Section per Präfix-Filter zu finden. Für die meisten Konfigurationsdateien mit einer flachen Section-Hierarchie ist dieser Ansatz völlig ausreichend und deutlich lesbarer als verschachtelte Arrays über Umwege zu simulieren.
#!/usr/bin/env bash
set -euo pipefail
declare -A ini_data
parse_ini() {
local file="$1" section=""
while IFS= read -r line; do
line="${line%%[#;]*}"
line="$(echo "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
[[ -z "$line" ]] && continue
if [[ "$line" =~ ^\[([^]]+)\]$ ]]; then
section="${BASH_REMATCH[1]}"
elif [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then
local key="${BASH_REMATCH[1]}" value="${BASH_REMATCH[2]}"
key="$(echo "$key" | sed -e 's/[[:space:]]*$//')"
ini_data["${section}.${key}"]="$value"
fi
done < "$file"
}
parse_ini "app.ini"
echo "${ini_data[database.host]}"
echo "${ini_data[database.port]}"
5. Kommentare, leere Zeilen und Whitespace robust behandeln
Ein häufiger Fehler in einfachen Parsern ist, Kommentarzeichen blind mit einem Textschnitt zu entfernen, ohne zu prüfen, ob sich das Zeichen innerhalb eines gequoteten Werts befindet. Der Ausdruck line="${line%%[#;]*}" aus den vorherigen Beispielen ist bewusst simpel gehalten und schneidet auch dann ab, wenn ein # mitten in einem Wert steht, was für die meisten praktischen Konfigurationsdateien ausreichend, aber nicht hundertprozentig korrekt ist.
Leere Zeilen und Zeilen, die nur aus Whitespace bestehen, sollten nach dem Trimmen konsequent übersprungen werden, statt als leere Schlüssel in das Ergebnis-Array zu wandern. Ebenso wichtig ist, Whitespace sowohl vor dem Schlüssel als auch um das Gleichheitszeichen zu entfernen, weil INI-Dateien in der Praxis stark unterschiedlich formatiert sind, je nachdem, welches Tool sie zuletzt geschrieben oder ein Mensch sie zuletzt von Hand bearbeitet hat.
6. Werte mit Anführungszeichen und Sonderzeichen: die eigentliche Quoting-Falle
Viele INI-Dateien umschließen Werte optional mit doppelten Anführungszeichen, meist um führende oder nachfolgende Leerzeichen zu erhalten oder um ein # im Wert selbst vor der Kommentar-Erkennung zu schützen. Ein naiver Parser, der diese Anführungszeichen nicht explizit entfernt, liefert Werte wie "geheimespasswort" statt geheimespasswort zurück, inklusive der Anführungszeichen als Teil des Strings, was nachgelagerte Vergleiche und Verwendungen des Werts unbemerkt kaputt macht.
Eine kleine Hilfsfunktion, die führende und nachfolgende Anführungszeichen mit einer Parameter-Expansion wie value="${value%\"}"; value="${value#\"}" entfernt, löst den häufigsten Fall zuverlässig. Escaped Anführungszeichen innerhalb eines Werts, also ein Anführungszeichen, das tatsächlich Teil des Inhalts sein soll, sind dagegen genau die Stelle, an der ein selbstgebauter Bash-Parser an seine Grenzen stößt, weil eine korrekte Behandlung einen echten Tokenizer statt einer einfachen Regex braucht.
#!/usr/bin/env bash
set -euo pipefail
strip_quotes() {
local value="$1"
# Remove one matching pair of surrounding double quotes, if present
sed -e 's/^"//' -e 's/"$//' <<< "$value"
}
strip_quotes '"hello world"' # -> hello world
strip_quotes 'no-quotes-here' # -> no-quotes-here
7. Grenzen eines selbstgebauten Parsers gegenüber echten INI-Parsern
So praktisch ein selbstgebauter Bash-Parser für einfache Konfigurationsdateien ist, er stößt an klare Grenzen. Verschachtelte Sections, wie sie manche INI-Dialekte über Punktnotation im Section-Namen simulieren, mehrzeilige Werte mit Fortsetzungszeilen, oder Werte-Interpolation, bei der eine Zeile auf einen zuvor gesetzten Wert verweist, lassen sich mit einer einfachen while-read-Schleife entweder gar nicht oder nur mit erheblichem Zusatzaufwand abbilden.
Ebenfalls fehlt einem selbstgebauten Parser jede Typprüfung: Alles wird als String eingelesen, eine Unterscheidung zwischen Zahl, Boolean und String muss der Aufrufer selbst treffen, inklusive der üblichen Bash-Eigenheit, dass true und false als Werte nur Strings sind und kein natives Boolean-Verhalten auslösen. Wer solche Anforderungen hat, sollte diese bewusst gegen die Einfachheit des selbstgebauten Ansatzes abwägen, statt sie nachträglich mühsam in Bash nachzubauen.
8. Wann sich ein externes Tool trotzdem lohnt
Sobald eine Konfigurationsdatei verschachtelte Strukturen, Arrays als Werte oder eine strikte Schema-Validierung braucht, ist der Wechsel zu einem echten Parser meist die bessere Investition. Pythons eingebautes configparser-Modul deckt praktisch den gesamten INI-Sprachumfang inklusive Interpolation ab und lässt sich aus einem Bash-Skript heraus mit einem einzeiligen python3 -c-Aufruf nutzen, ohne dass das restliche Skript in Python umgeschrieben werden muss.
Für Projekte, die ohnehin schon yq oder jq im Werkzeugkasten haben, bietet sich außerdem an, die INI-Datei einmalig in JSON oder YAML zu konvertieren und ab dort mit den bereits vorhandenen, gut getesteten Tools weiterzuarbeiten, statt Quoting- und Escaping-Regeln in Bash-Regex nachzubilden. Die Faustregel lautet: Ein selbstgebauter Parser lohnt sich für kleine, gut bekannte, flache Konfigurationsdateien; alles, was darüber hinausgeht, verdient ein echtes Parsing-Werkzeug.
9. Vollständiges Beispiel und Vergleich der Ansätze
Die folgende Tabelle fasst zusammen, wann ein selbstgebauter Bash-Parser ausreicht und wann sich der Griff zu einem externen Tool auszahlt, abhängig von der Komplexität der Konfigurationsdatei und den Anforderungen an Robustheit.
| Anforderung | Eigener Bash-Parser | python3 configparser | yq/jq nach Konvertierung |
|---|---|---|---|
| Flache Sections, einfache Werte | Gut geeignet | Funktioniert, mehr Overhead | Funktioniert, mehr Overhead |
| Anführungszeichen in Werten | Manuell mit Parameter-Expansion | Nativ unterstützt | Nach Konvertierung nativ |
| Werte-Interpolation | Nicht praktikabel | Nativ unterstützt | Nicht anwendbar |
| Verschachtelte Strukturen | Nicht praktikabel | Begrenzt | Sehr gut, nach Konvertierung |
| Keine externe Abhängigkeit | Ja | Nein, braucht Python | Nein, braucht yq/jq |
Mironsoft
Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur
Shell-Skripte, die in der Produktion zuverlässig laufen?
Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.
Code-Review
ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.
Refactoring
Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.
CI-Integration
ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.
10. Zusammenfassung
INI-Parsing in Bash: Das Wichtigste auf einen Blick
Grundstruktur
while IFS= read -r line kombiniert mit Bash-Regex-Matching über [[ $line =~ ... ]] erkennt Sections und Key-Value-Zeilen zuverlässig.
Speicherung
Assoziative Arrays mit zusammengesetztem Schlüssel section.key bilden zweistufige INI-Strukturen in einem flachen Bash-Array ab.
Quoting
Anführungszeichen um Werte müssen explizit mit Parameter-Expansion entfernt werden, sonst landen sie unbemerkt im geparsten Wert.
Grenzen
Verschachtelung, Interpolation und Typprüfung sind mit reinem Bash nicht praktikabel; ab dann lohnt sich configparser oder yq.