Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

env.parser

import env.parser

env lifts part of this module out to its own top level; each name below is shown with the path that reaches it. Anything still spelled env.parser.* needs import env.parser.

The scanner that turns the text of an environment file into names and values, and the writer that turns them back into text.

The grammar is small enough to state in full. A source is a sequence of lines. A line is blank, a comment, or an assignment:

assignment := 'export'? NAME '=' value?
NAME       := [A-Za-z_][A-Za-z0-9_]*
value      := bare | '...' | "..." | `...`

Everything interesting is in what each kind of value means, and that is documented on parse() where people will look for it.

Functions

is_name()

env.is_name(name: string) -> bool

Returns true when name is a legal environment variable name.

That means a letter or underscore, then any number of letters, digits and underscores. It is the same rule POSIX shells apply, and the same one the parser and stringify() enforce.

Parameters

  • name (string)

Returns bool

parse()

env.parse(source: string|bytes) -> dict

Reads the text of an environment file and returns its names and values.

This is the syntax layer on its own: no $ reference is resolved and nothing is written to the process environment. load() is what does both.

import env

var values = env.parse('PORT=8080\nGREETING="hello there"')

echo values.PORT
echo values.GREETING
8080
hello there

The rules

A name is a letter or underscore followed by letters, digits and underscores. Anything else is a ParseError rather than a line quietly dropped, because a name a shell could never export is a typo every time.

Blank lines are skipped, and so is everything from a # to the end of the line. Inside an unquoted value a # only opens a comment when it starts the value or follows a space, so KEY=db#1 keeps its #.

A leading export is allowed and ignored, so the same file can be fed to source in a shell.

Values come in four forms:

WrittenMeans
KEY=valuethe text up to a comment or the end of the line, trimmed at both ends
KEY='value'every character exactly as written, newlines included
KEY=`value`the same as single quotes, for values that contain both kinds
KEY="value"backslash escapes resolved, $ references left for expansion

KEY= is an empty value, and an empty value counts as unconfigured everywhere else in this module.

Inside double quotes, \n, \r, \t, \f, \v, \b, \a, \e and \0 mean what they do in Zuri, \xHH, \uHHHH and \u{H...} name a codepoint, a backslash before a newline joins the two lines, and \$ is a literal $ that expansion will not touch. A backslash before anything else keeps both characters, so "C:\Users" survives intact.

Line endings are normalised to \n, and a byte-order mark at the head of the file is discarded.

Duplicates

The last assignment to a name in a source wins, which is what makes commenting out the line above the one you want work.

Parameters

  • source (string|bytes)

Returns dict

Raises ParseError when the source is not a valid environment file.

stringify()

env.stringify(values: dict) -> string

Turns a dictionary of names and values into the text of an environment file.

The output round-trips: feeding it back to parse() returns the same names and the same values. Values are written bare where that is unambiguous and double-quoted where it is not, and a $ inside a value is escaped so that loading the file back does not expand it.

import env

print(env.stringify({
  PORT: 8080,
  GREETING: 'hello there',
  DEBUG: false,
}))
PORT=8080
GREETING="hello there"
DEBUG=false

Names are written in the order the dictionary holds them. A number, a bigint, a boolean or a bytes value is converted to its text; any other type is a TypeError, because guessing what a list should look like in an environment file is how configuration goes wrong silently.

Parameters

  • values (dict)

Returns string

Raises ValueError when a key is not a legal variable name.

Raises TypeError when a value is of a type that has no spelling here.


2026, Richard Ore and Zuri contributors