The Environment
The environment is a list of KEY=value strings that the parent process
decided on and the kernel copied into your address space at startup. It is not
a system-wide setting, it is not shared, and nothing you do to it after startup
reaches any other running process.
const std = @import("std");
pub fn main(init: std.process.Init) !void {
var buf: [1024]u8 = undefined;
var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &buf);
const out = &stdout_writer.interface;
// The real environment, already parsed into a map by the startup code.
// This program is running in a wasm sandbox with no parent process to
// inherit from, so the count is the honest answer rather than a stub.
const env = init.environ_map;
try out.print("inherited variables: {d}\n", .{env.count()});
try out.print("PATH: {?s}\n\n", .{env.get("PATH")});
// Putting a variable in the map does not change any other process. The
// map is this program's copy; a child gets one when it is spawned.
try env.put("ZIG_GUIDE", "live");
try env.put("EDITOR", "vim");
try out.print("after two puts: {d}\n", .{env.count()});
try out.print("ZIG_GUIDE: {?s}\n", .{env.get("ZIG_GUIDE")});
try out.print("contains EDITOR: {}\n", .{env.contains("EDITOR")});
_ = env.swapRemove("EDITOR");
try out.print("after removing it: {?s}\n\n", .{env.get("EDITOR")});
// A name may not be empty and may not contain '='. The separator is not
// escapable, so a key holding one could never be read back.
try out.print("\"HOME\" valid: {}\n", .{std.process.Environ.Map.validateKeyForPut("HOME")});
try out.print("\"A=B\" valid: {}\n", .{std.process.Environ.Map.validateKeyForPut("A=B")});
try out.flush();
}It arrives already parsed
pub fn main(init: std.process.Init) !void {
const env = init.environ_map;
const path = env.get("PATH");
}
init.environ_map is a *std.process.Environ.Map that the startup code
populated from the raw block before main was called, allocated with the same
gpa on init. You do not free it and you do not build it.
The count is 0 in the playground above. That is not a stub standing in for the
real thing: a wasm module in a browser tab has no parent process to inherit an
environment from, so the honest answer is that there are none. Reading PATH
there returns null, which is exactly what a program should be prepared for on
any platform. A variable you did not set is a variable that may not be there.
Your copy is yours
try env.put("ZIG_GUIDE", "live");
That changes this program’s map and nothing else. It does not modify the shell
that launched you, and on its own it does not even modify what a child of yours
will see: a child inherits a copy of the block that existed when it was
spawned, and you choose which block that is by passing environ_map to
spawn. Leave it null and the child gets the parent’s.
The direction that surprises people is the one that does not work. There is no call that lets a program change its parent’s environment, which is why every tool that wants to alter your shell prints something for the shell to evaluate instead:
eval "$(direnv hook bash)"
That is not a workaround for a missing API. It is the only way, because the environment was copied at fork time and the copies were never connected.
Keys have two rules
A name may not be empty, and may not contain = or a NUL byte. The map will
tell you:
std.process.Environ.Map.validateKeyForPut("A=B") // false
The separator is not escapable. A block is a flat list of KEY=value strings
split on the first =, so a key containing one could be written but never read
back as what you meant. Everything after that first byte is value, including
newlines, spaces and further = signs, which is why PATH works and why a
value never needs quoting at this level. The quoting you write in a shell is
the shell’s, and it is gone by the time the string reaches you.
Case, and the platform that disagrees
Names are case-sensitive on POSIX and case-insensitive on Windows, where the
block is UTF-16 and Path and PATH are the same variable. Environ.Map
matches the platform it was built for. Code that reads one spelling and writes
another is fine on Windows and quietly broken everywhere else, so pick the
conventional upper-case spelling and use it in both places.