std.ini
INI-Konfigurationen lesen und schreiben: Sektionen ([server]), Schlüssel-Wert-Zeilen, Kommentare mit # und ;, typisierte Abfragen mit Vorgabewert, Iteration über Sektionen und Schlüssel, Laden und Speichern.
→ std.string · std.fs · Standard Library
Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind echte Programmausgaben.
Einsatz: Anwendungs- und Dienstkonfiguration, Embedded-Systeme, Migrationsskripte — überall dort, wo eine lesbare Schlüssel-Wert-Datei ohne XML- oder JSON-Aufwand genügt.
Wie das Dokument aussieht
Das „Dokument-Handle„ ist eine Kopie des INI-Textes im Speicher, kein Datenmodell. ParseString kopiert die Eingabe, alle weiteren Funktionen durchsuchen diesen Text bei jedem Aufruf von vorn.
Das hat zwei angenehme Folgen:
- Kommentare, Reihenfolge und Formatierung bleiben erhalten — ein Durchlauf Laden → Ändern → Speichern erhält die Datei so, wie sie war, samt Kopfzeilen und Einrückung.
- Der Zustand ist einfach:
doc as pcharist der vollständige Text und lässt sich direkt ausgeben.
Und eine unangenehme:
Der Dokumentpuffer ist genau so groß wie der Ausgangstext.ParseStringlegtalloc(len + 1)an; die Änderungsfunktionen schreiben ihr Ergebnis anschließend in denselben Puffer zurück. Wird der Text dabei länger — bei jedem neuen Schlüssel, jeder neuen Sektion, jedem längeren Wert — schreibt die Unit über das Pufferende hinaus in fremden Speicher (#1426).
Nachgemessen: ein Dokument aus[a]\nk=1\n(9 Byte Puffer) enthält nach zweiSetString-Aufrufen 108 Zeichen, der Nachbarblock ist überschrieben.
Bis das behoben ist: Entweder nur lesen, oder den Ausgangstext von vornherein groß genug wählen — etwa indem alle Schlüssel bereits mit Platzhalterwerten in der Datei stehen undSetStringsie nur ersetzt (gleich lang oder kürzer).
Lesen
| Signatur | Beschreibung |
|---|---|
ParseString(input: pchar): int64 | Kopiert den Text und liefert das Handle; 0 bei leerer Eingabe |
LoadFile(path: pchar): int64 | Datei einlesen und Handle liefern |
GetString(doc, section, key, default_val: pchar): pchar | Wert oder Vorgabe |
GetInt(doc, section, key, default_val: int64): int64 | wie oben, als Ganzzahl |
GetBool(doc, section, key, default_val: bool): bool | true/1/yes/on gelten als wahr |
GetFloat(doc, section, key, default_val: f64): f64 | als Fließkommazahl |
HasSection(doc, section): bool · HasKey(doc, section, key): bool | Bestandsprüfung |
GetSectionCount(doc): int64 · GetKeyCount(doc, section): int64 | Anzahl |
Werte werden getrimmt, und alles ab dem ersten # oder ; gilt als Kommentar — auch mitten in der Zeile.
import std.ini;
import std.alloc;
import std.string;
fn J(b: bool): pchar { if (b) { return "ja"; } return "nein"; }
fn main(): int64 {
var text: pchar := "; Konfiguration\n[server]\nhost = example.org ; Kommentar am Zeilenende\nport=8080\ndebug = true\nfaktor = 1.5\n\n[leer]\n"c;
var doc: int64 := ParseString(text);
// Typisierte Getter, jeweils mit Vorgabewert
PrintLn(StrConcat("host: ", GetString(doc, "server"c, "host"c, "?"c)));
PrintLn(StrConcat("port: ", IntToStr(GetInt(doc, "server"c, "port"c, 0))));
PrintLn(StrConcat("debug: ", J(GetBool(doc, "server"c, "debug"c, false))));
PrintLn(StrConcat("fehlt: ", GetString(doc, "server"c, "gibtsnicht"c, "Vorgabe"c)));
PrintLn(StrConcat("Sektion fehlt: ", GetString(doc, "andere"c, "host"c, "Vorgabe"c)));
// Bestand pruefen
PrintLn(StrConcat("HasSection('server'): ", J(HasSection(doc, "server"c))));
PrintLn(StrConcat("HasKey('server','port'): ", J(HasKey(doc, "server"c, "port"c))));
PrintLn(StrConcat("Sektionen insgesamt: ", IntToStr(GetSectionCount(doc))));
PrintLn(StrConcat("Schluessel in [server]: ", IntToStr(GetKeyCount(doc, "server"c))));
return 0;
}
host: example.org
port: 8080
debug: ja
fehlt: Vorgabe
Sektion fehlt: Vorgabe
HasSection('server'): ja
HasKey('server','port'): ja
Sektionen insgesamt: 2
Schluessel in [server]: 4
GetStringlegt für jeden Treffer einen neuen Puffer an und gibt ihn nie frei — eine Freigabefunktion gibt es in der Unit nicht (#1428). 1000 Abfragen kosten 1000 Blöcke.GetInt,GetBoolundGetFloatrufenGetStringintern auf und verhalten sich genauso.
Für einen langlaufenden Dienst heißt das: Konfiguration einmal beim Start auslesen und die Werte selbst vorhalten, nicht bei jedem Zugriff neu abfragen.
Auflisten
| Signatur | Beschreibung |
|---|---|
GetSections(doc, output: pchar, max_count: int64): int64 | schreibt die Sektionsnamen, liefert deren Anzahl |
GetKeys(doc, section, output: pchar, max_count: int64): int64 | dasselbe für die Schlüssel einer Sektion |
Ausgabeformat: Die Namen liegen dicht hintereinander, jeweils nullterminiert — kein festes Raster. Zum nächsten Namen kommt man über p + StrLen(p) + 1.
import std.ini;
import std.alloc;
import std.string;
// Sektions- und Schluesselnamen liegen dicht hintereinander, je nullterminiert
fn Naechster(p: int64): int64 { return p + StrLen(p as pchar) + 1; }
fn main(): int64 {
// ACHTUNG: Der Dokumentpuffer ist nur so gross wie der Ausgangstext (#1426).
// Deshalb hier von vornherein genug Text - oder gar nicht erst aendern.
var doc: int64 := ParseString("[server]\nhost = example.org\nport = 8080\n\n[logging]\nlevel = info\n"c);
var buf: int64 := alloc(4096);
var i: int64 := 0;
while (i < 4096) { poke8(buf + i, 0); i := i + 1; }
var n: int64 := GetSections(doc, buf as pchar, 16);
PrintLn(StrConcat("Sektionen: ", IntToStr(n)));
var p: int64 := buf;
i := 0;
while (i < n) { PrintLn(StrConcat(" ", p as pchar)); p := Naechster(p); i := i + 1; }
i := 0;
while (i < 4096) { poke8(buf + i, 0); i := i + 1; }
n := GetKeys(doc, "server"c, buf as pchar, 16);
PrintLn(StrConcat("Schluessel in [server]: ", IntToStr(n)));
p := buf; i := 0;
while (i < n) { PrintLn(StrConcat(" ", p as pchar)); p := Naechster(p); i := i + 1; }
// Zeilen von Hand bauen - fuer eigene Ausgabe ohne Set...
var o: pchar := alloc(256) as pchar;
BuildSectionHeader("db"c, o); PrintLn(StrConcat("Kopfzeile: ", o));
BuildKeyValueLine("user"c, "lyx"c, o); PrintLn(StrConcat("Wertzeile: ", o));
// Namen pruefen, bevor sie geschrieben werden
PrintLn(StrConcat("IsValidSectionName('a b'): ", IntToStr(IsValidSectionName("a b"c) as int64)));
PrintLn(StrConcat("IsValidKeyName('po rt'): ", IntToStr(IsValidKeyName("po rt"c) as int64)));
return 0;
}
Sektionen: 2
server
logging
Schluessel in [server]: 2
host
port
Kopfzeile: [db]
Wertzeile: user=lyx
IsValidSectionName('a b'): 0
IsValidKeyName('po rt'): 0
Ändern und Speichern
| Signatur | Beschreibung |
|---|---|
SetString(doc, section, key, value): void | Setzt oder ersetzt; legt Sektion und Schlüssel bei Bedarf an |
SetInt · SetBool · SetFloat | Hüllen um SetString |
DeleteKey(doc, section, key): bool · DeleteSection(doc, section): bool | Entfernen |
WriteString(doc, output, max_len): int64 | Dokument in einen Puffer schreiben |
WriteFile(doc, path): bool · SaveFile(doc, path): bool | in eine Datei schreiben |
Alle Set…-Funktionen geben nichts zurück — ob die Änderung gelungen ist, lässt sich nur über ein anschließendes HasKey oder GetString feststellen. Wegen #1426 sind sie nur sicher, wenn das Ergebnis nicht länger wird als der Ausgangstext.
Die Löschfunktionen verkürzen den Text und sind deshalb unbedenklich; ein Durchlauf Laden → Löschen → Speichern erhält Kommentare und Reihenfolge:
; Kopfkommentar
[s]
k = 1 # Zeilenkommentar
bleibt nach LoadFile + SaveFile unverändert erhalten.
Zeilen und Namen von Hand
| Signatur | Beschreibung |
|---|---|
BuildSectionHeader(section, output): int64 | [name] |
BuildKeyValueLine(key, value, output): int64 | key=value |
ParseLine(line, line_len, key_out, value_out): bool | Zerlegt eine Schlüssel-Wert-Zeile |
IsSectionLine(line, line_len): bool | Ist die Zeile ein Sektionskopf? |
ParseSectionLine(line, line_len, output): int64 | Sektionsnamen aus der Kopfzeile |
IsValidSectionName(name): bool · IsValidKeyName(key): bool | Namen prüfen — Leerzeichen sind unzulässig |
EscapeValue(input, output): int64 · UnescapeValue(input, output): int64 | Sonderzeichen im Wert |
Diese Bausteine erlauben es, eine INI-Datei zusammenzusetzen, ohne die Set…-Funktionen zu benutzen — der derzeit sicherste Weg, eine Datei neu zu schreiben.
EscapeValuemaskiert;und#nicht — obwohl der eigene Parser beim ersten dieser Zeichen abschneidet. Ein WertHallo; Weltkommt beim Lesen alsHallozurück (#1427). Behandelt wird nur der Zeilenumbruch (\n), dazu\tund\\.
Wer Werte mit Semikolon, Rautezeichen oder führenden Leerzeichen speichern muss, kodiert sie selbst — etwa mit std.base64.
Konstanten
| Konstante | Wert | Wirkung |
|---|---|---|
MAX_SECTIONS | 64 | keine |
MAX_ENTRIES_PER_SECTION | 128 | keine |
MAX_KEY_LENGTH | 64 | keine |
MAX_VALUE_LENGTH | 256 | keine |
MAX_LINE_LENGTH | 512 | keine |
INI_BUFFER_SIZE | 65536 | Größe des Zwischenpuffers beim Ändern |
Die fünf Grenzwerte werden nirgends geprüft (#1429). Sie stammen aus einer früheren, tabellenbasierten Fassung; seit das Dokument roher Text ist, gibt es weder eine Sektionstabelle noch feste Feldlängen. Ein Schlüssel mit 70 Zeichen wird anstandslos gesetzt und gefunden, mehr als 64 Sektionen ebenso.
Die einzige wirksame Grenze istINI_BUFFER_SIZE: Beim Ändern baut die Unit das Ergebnis in einem 64-KB-Puffer auf. Ein Dokument, das darüber hinauswächst, läuft über — ebenfalls ungeprüft.
Kommentare hinzufügen
AddSectionCommentundAddKeyCommentsind nicht umgesetzt und brechen ab. Beide melden seit lyxc 1.0.15Gstd.ini: AddSectionComment ist nicht umgesetzt (#1244)bzw.… AddKeyComment …und beenden das Programm. Vorher liefen sie still durch, ohne einen Kommentar zu schreiben — der Aufruf sah wie ein Erfolg aus.
Kommentare gehören also beim Zusammensetzen der Datei selbst gesetzt (BuildSectionHeader+ eigene; …-Zeilen). Beim Lesen und beim Roundtrip bleiben vorhandene Kommentare dagegen erhalten.
Fallstricke
- Nur lesen ist sicher. Jedes
Set…, das den Text verlängert, überschreibt fremden Speicher (#1426). - Jede Abfrage kostet Speicher (#1428) und durchsucht den gesamten Text linear — bei großen Dateien in Schleifen spürbar.
;und#im Wert beenden ihn — auch ohne Leerzeichen davor.- Schlüssel- und Sektionsnamen werden nicht auf Gültigkeit geprüft, wenn man sie über
SetStringsetzt;IsValidKeyNamemuss man selbst aufrufen. - Groß- und Kleinschreibung zählt bei Sektions- und Schlüsselnamen.
- Kein Handle-Abbau. Weder
ParseStringnochLoadFilehaben ein Gegenstück; der Dokumentpuffer bleibt bis zum Programmende belegt. - Doppelte Schlüssel: Gefunden wird der erste Treffer in der Sektion;
SetStringersetzt ebenfalls nur diesen.
Weiterführend: std.json · std.yaml · std.env · Standard Library
Quelle: std/ini.lyx (1069 Zeilen) · Autor: Andreas Röne · Copyright: 2024–2025 Andreas Röne
Letzte Aktualisierung: 2026-08-13 — Seite gegen std/ini.lyx überarbeitet: zwei lauffähige Beispiele mit echter Ausgabe, das textbasierte Dokumentmodell und das Ausgabeformat von GetSections/GetKeys (dicht gepackt, nullterminiert) erstmals beschrieben, Fallstricke ergänzt. Belegt und als Issue erfasst: Dokumentpuffer wächst nicht mit (#1426), EscapeValue maskiert ;/# nicht (#1427), GetString leckt je Aufruf (#1428), die Grenzkonstanten sind wirkungslos (#1429). Geprüft mit lyxc 1.0.21A.
