std.env — Umgebung und Kommandozeile
Zugriff auf Umgebungsvariablen und Kommandozeilenargumente — beides direkt aus dem Startstack, ohne C-Bibliothek. Der Kernel legt beim Prozessstart hinter argv den Zeigervektor der Umgebung ab; std.env liest ihn dort aus.
Einsatzbereiche: CLI-Werkzeuge, Dienste und Skript-Wrapper, die ihre Konfiguration aus –flag=wert oder Umgebungsvariablen beziehen.
Autor: Andreas Röne
Copyright: 2024–2025 Andreas Röne
Quelle: std/env.lyx
<WRAP info>
std.env ist die Grundlage für die Umgebungsfunktionen in std.os: env_get ruft intern EnvLookupRaw auf. Wer nur lesen will, kommt mit std.env aus; wer Werte zur Laufzeit setzen will, braucht std.os — siehe Zusammenspiel mit std.os.
</WRAP>
Import
import std.env;
Funktionen
Umgebungsvariablen
| Signatur | Beschreibung |
|---|---|
EnvLookupRaw(name: pchar): pchar | Wert der Variablen name, oder 0 wenn nicht gesetzt. Der Zeiger zeigt in die Umgebung, es wird nichts kopiert |
EnvBase(): int64 | Adresse des envp-Vektors, oder 0 wenn argv nicht gesichert wurde |
EnvEntryValueOff(entry: pchar, key: pchar): int64 | Prüft einen Eintrag der Form NAME=WERT gegen key; liefert den Offset des Werts im Eintrag oder -1 |
EnvBase und EnvEntryValueOff sind die Bausteine von EnvLookupRaw und im Alltag selten nötig — sie sind öffentlich, damit man eigene Durchläufe über die gesamte Umgebung schreiben kann.
Auch unter Windows nutzbar.EnvBaseunterstellte das SysV-Speicherbild (envphinterargv); beim Zielwin64stürzteEnvLookupRawdeshalb sofort ab — samt allem, was darauf aufsetzt (env_getausstd.os, die Zeitzonenauswahl instd.datetime). Seit lyxc 1.1.3F wird dort der Windows-Umgebungsblock gelesen (#1677); nachgemessen mit einem PE unter Wine.
Kommandozeile
| Signatur | Beschreibung |
|---|---|
ArgvGetStr(argc: int64, argv: pchar, name: pchar): pchar | Sucht ein Argument der Form –name=wert und gibt den Wertteil zurück, sonst 0 |
Dazu die Compiler-Builtins, die den Startstack bereitstellen:
| Builtin | Beschreibung |
|---|---|
GetArgC(): int64 | Anzahl der Argumente einschließlich Programmname |
GetArgV(): pchar | Basis des argv-Zeigervektors |
ArgvGet(argv: pchar, i: int64) | Argument i; außerhalb von 0…argc kommt der Nullzeiger zurück |
Beispiele
Umgebungsvariablen lesen
import std.env;
fn zeigVar(name: pchar): void {
var v: pchar := EnvLookupRaw(name);
if (v != 0 as pchar) { PrintLn(" ", name, " = ", v); }
else { PrintLn(" ", name, " ist nicht gesetzt"); }
}
fn main(): int64 {
zeigVar("HOME");
zeigVar("LANG");
zeigVar("GIBTESNICHT_XYZ");
// Praefixe schlagen nicht faelschlich an
zeigVar("HOM");
zeigVar("HOMEX");
return 0;
}
Ausgabe:
HOME = /home/andreas
LANG = de_DE.UTF-8
GIBTESNICHT_XYZ ist nicht gesetzt
HOM ist nicht gesetzt
HOMEX ist nicht gesetzt
Der Vergleich ist exakt: HOM schlägt bei einem Eintrag HOME=… nicht an, weil hinter dem Namen zwingend ein = stehen muss. Werte dürfen Leerzeichen enthalten — der Eintrag endet erst am Nullbyte.
Nicht gesetzt wird durch den Nullzeiger angezeigt, nicht durch einen leeren String. Eine gesetzte, aber leere Variable (FOO=) liefert einen gültigen Zeiger auf einen leeren String — der Unterschied ist also erkennbar.
Kommandozeilenargumente auswerten
import std.env;
fn hole(name: pchar, standard: pchar): pchar {
var v: pchar := ArgvGetStr(GetArgC(), GetArgV(), name);
if (v == 0 as pchar) { return standard; }
return v;
}
fn main(): int64 {
PrintLn("Aufruf mit ", IntToStr(GetArgC()), " Argumenten:");
var i: int64 := 0;
while (i < GetArgC()) {
PrintLn(" argv[", IntToStr(i), "] = ", ArgvGet(GetArgV(), i) as pchar);
i := i + 1;
}
PrintLn("Konfiguration:");
PrintLn(" Ausgabedatei: ", hole("output", "a.out"));
PrintLn(" Stufe: ", hole("level", "1"));
PrintLn(" Modus: ", hole("mode", "standard"));
return 0;
}
Aufruf ./d2 –output=bericht.pdf –level=3:
Aufruf mit 3 Argumenten:
argv[0] = ./d2
argv[1] = --output=bericht.pdf
argv[2] = --level=3
Konfiguration:
Ausgabedatei: bericht.pdf
Stufe: 3
Modus: standard
Das Muster hole(name, standard) ist die übliche Form: ArgvGetStr liefert 0 bei fehlendem Argument, der Aufrufer setzt den Vorgabewert ein.
Zusammenspiel mit std.os
std.os bietet env_get, env_set, env_unset und env_has. Der Lesepfad landet bei EnvLookupRaw; Schreiben geht dagegen nicht in den Startstack, sondern in eine prozesseigene Überlagerung, die env_get vorgeschaltet ist.
import std.env;
import std.os;
fn main(): int64 {
PrintLn("env_get(HOME) = ", env_get("HOME"));
PrintLn("EnvLookupRaw(HOME) = ", EnvLookupRaw("HOME"));
PrintLn("env_has(MEINE_VAR) = ", IntToStr(env_has("MEINE_VAR") as int64));
env_set("MEINE_VAR", "gesetzt-zur-laufzeit");
PrintLn("nach env_set:");
PrintLn(" env_get(MEINE_VAR) = ", env_get("MEINE_VAR"));
var raw: pchar := EnvLookupRaw("MEINE_VAR");
PrintLn(" EnvLookupRaw(MEINE_VAR) gesetzt? ", IntToStr((raw != 0 as pchar) as int64), " <- Ueberlagerung nicht sichtbar");
env_unset("HOME");
PrintLn("nach env_unset(HOME):");
PrintLn(" env_get(HOME) = '", env_get("HOME"), "'");
PrintLn(" EnvLookupRaw(HOME) = '", EnvLookupRaw("HOME"), "' <- Startstack unveraendert");
return 0;
}
Ausgabe:
env_get(HOME) = /home/andreas
EnvLookupRaw(HOME) = /home/andreas
env_has(MEINE_VAR) = 0
nach env_set:
env_get(MEINE_VAR) = gesetzt-zur-laufzeit
EnvLookupRaw(MEINE_VAR) gesetzt? 0 <- Ueberlagerung nicht sichtbar
nach env_unset(HOME):
env_get(HOME) = ''
EnvLookupRaw(HOME) = '/home/andreas' <- Startstack unveraendert
Daraus folgen drei Regeln:
- Wer mit
env_setarbeitet, muss durchgängigenv_getlesen —EnvLookupRawsieht die Überlagerung nicht. env_unsetentfernt nichts aus dem Startstack, es blendet den Namen nur fürenv_getaus.- Ein per
execvegestartetes Kind erbt die Überlagerung nicht — es bekommt die unveränderte Umgebung des Elternprozesses.
Unterschiedlich ist auch der Rückgabewert bei fehlender Variable: EnvLookupRaw liefert den Nullzeiger, env_get einen leeren String. Bei env_get sind „nicht gesetzt„ und „auf leer gesetzt“ daher nicht unterscheidbar; dafür gibt es env_has.
Fallstricke
Geprüft mit lyxc 1.0.21A.
Argumentschleifen enden am Nullzeiger
argv[argc] ist der abschließende Nullzeiger — die verlässliche Abbruchbedingung. Seit dem Fix von #1460 liefert auch jeder Index darüber hinaus 0, statt in die dahinterliegende Umgebung zu lesen:
argc = 2
argv[argc] = 0
argv[argc+1] = 0
Ein Off-by-one führt damit zu einem Nullzeiger statt zu einer Zeichenkette aus der Umgebung. Schleifen trotzdem gegen GetArgC() begrenzen — das ist die klarere Absicht.
Der Rückgabewert zeigt in die Umgebung
EnvLookupRaw kopiert nicht. Der Zeiger bleibt gültig, solange der Prozess lebt — die Startstack-Umgebung wird nicht verändert. Wer den Wert aber selbst umschreiben will, muss ihn vorher kopieren; ein poke8 auf den Rückgabewert verändert die Umgebung des laufenden Prozesses.
ArgvGet in PrintLn braucht einen Cast
Ohne as pchar gibt PrintLn(ArgvGet(GetArgV(), i)) die Zeigeradresse als Zahl aus:
argv[0] = 140721192761205
Mit ArgvGet(GetArgV(), i) as pchar erscheint der Text. Das betrifft nur die Ausgabe; ArgvGetStr und StrLen verarbeiten den Wert auch ohne Cast korrekt.
Der Flag-Name wird ohne führende Striche übergeben
ArgvGetStr(argc, argv, „output“) findet –output=…. Die beiden Striche prüft die Funktion selbst — ArgvGetStr(argc, argv, „–output“) findet folglich nur —-output=… und damit praktisch nichts. Frühere Fassungen dieser Seite zeigten die Form mit Strichen.
Nur --name=wert, nichts anderes
Nicht unterstützt: getrennte Form (–output datei), Kurzformen (-o wert), Schalter ohne Wert (–verbose) und die Endemarke –. –name= mit leerem Wert liefert einen gültigen Zeiger auf einen leeren String, nicht 0. Wer mehr braucht, wertet argv selbst aus.
Obergrenze von 8192 Einträgen
EnvLookupRaw bricht nach 8192 Umgebungseinträgen ab und meldet „nicht gesetzt„. In der Praxis unkritisch — die üblichen Grenzen des Kernels für Umgebungsgröße greifen weit vorher.
