INI-Dateien in reinem Bash parsen: Ohne externe Tools auslesen
AI generated
$_
#!/
Bash · Konfiguration · Parsing · Bordmittel
INI-Dateien in reinem Bash parsen
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.

16 Min. Lesezeit Assoziative Arrays · Bash 4+ read · Regex · Quoting

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.

11. FAQ: INI-Parsing in Bash: Das Wichtigste auf einen Blick

1Ab welcher Bash-Version funktionieren assoziative Arrays?
Assoziative Arrays mit declare -A sind ab Bash 4.0 verfügbar. macOS liefert standardmäßig noch Bash 3.2 aus, weshalb dort explizit eine über Homebrew installierte Bash 5.x verwendet werden muss.
2Warum sollte ich while IFS= read -r statt while read line verwenden?
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. Ohne beide Optionen werden manche Werte falsch geparst.
3Wie erkenne ich eine Section-Zeile mit Bash-Regex?
Mit einem Muster wie ^\[([^]]+)\]$ gegen [[ $line =~ ... ]]. Der Section-Name landet danach in BASH_REMATCH[1].
4Wie speichere ich zweistufige INI-Daten in Bash?
Am praktikabelsten ist ein einziges assoziatives Array mit zusammengesetztem Schlüssel wie section.key, weil Bash keine nativen verschachtelten Arrays kennt.
5Wie entferne ich Anführungszeichen um einen Wert?
Mit Parameter-Expansion wie value="${value%\"}" und value="${value#\"}", die ein abschließendes beziehungsweise führendes Anführungszeichen entfernen, sofern beide vorhanden sind.
6Kann ein selbstgebauter Parser verschachtelte Sections abbilden?
Nicht praktikabel. Verschachtelte Strukturen erfordern einen echten Tokenizer oder Parser, wie ihn Pythons configparser oder eine Konvertierung nach YAML mit anschließender yq-Verarbeitung bietet.
7Wie behandle ich Kommentare am Ende einer Wertzeile?
Ein einfacher Ansatz schneidet die Zeile am ersten # oder ; ab, was für die meisten Konfigurationsdateien ausreicht, aber Kommentarzeichen innerhalb gequoteter Werte fälschlich mit abschneidet.
8Wann lohnt sich Pythons configparser statt eines eigenen Bash-Parsers?
Sobald Werte-Interpolation, strikte Typprüfung oder komplexere Quoting-Regeln gebraucht werden. configparser deckt den vollen INI-Sprachumfang ab und lässt sich mit einem einzeiligen python3 -c-Aufruf aus Bash heraus nutzen.
9Ist ein selbstgebauter INI-Parser sicher gegen fehlerhafte Eingabedateien?
Nur bedingt. Ein einfacher Parser ignoriert unerwartete Zeilen meist stillschweigend statt einen Fehler zu werfen. Für produktive Systeme sollte zusätzlich eine Validierung der erwarteten Pflichtschlüssel nach dem Parsen erfolgen.
10Wie teste ich einen selbstgebauten INI-Parser zuverlässig?
Mit einer Reihe fest definierter Testdateien, die typische Edge Cases wie Anführungszeichen, leere Werte, Kommentare und fehlende Sections abdecken, kombiniert mit einem einfachen Testrunner wie BATS, der die geparsten Werte gegen erwartete Ausgaben prüft.