Expand environment variables and program arguments in a string, parse general and OS-specific .env files
Project description
envara
© Alexander Iurovetski 2026
A library to expand environment variables, application arguments and escape sequences in arbitrary strings, as well as to load stacked env files, remove line comments, unquote and expand values (if the input was unquoted or enclosed in single quotes), execute sub-commands, and finally extend the environment.
Does not depend on any special Python package.
The test suite covers 1175 tests with 100% code coverage across all modules.
Please note that version 0.4.0 brought breaking changes: a switch from multiple parameters (for various platform-specific characters) to a single object of the class EnvCharsData. It also decides on which platform's rules to use for the variables' expansions in env files based on the first non-empty character(s) representing a start of a line comment. Previously, it was searching for specific patterns in every line. Finally, public methods Env.expand_posix(...) and Env.expand_simple(...) have been moved to the private scope, so stop using those directly in favour of Env.expand(...).
Table of Contents
- Sample Usage
- Library Overview
- POSIX-style Expansions
- Simple Expansions for Windows, OpenVMS, and RiscOS
- Env File Lookup
- What Kind of Expansion to Choose in the Env Files?
- Good Luck!
Sample Usage
-
Install envara from PyPI
-
Create a .py file with the following content, then run it with 3 arbitrary arguments:
#!/usr/bin/env python3
###############################################################################
# envara (C) Alexander Iurovetski 2026
#
# An example of how to use the envara package
#
# Run it with 3 arguments like:
#
# python[3] [dir/]example.py v1 23 4
###############################################################################
import os
from pathlib import Path
import sys
# Remove this and the line below if the envara package is installed
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
from envara.env_chars import EnvChars
from envara.env import Env
from envara.env_file import EnvFile
###############################################################################
def main():
"""
Sample program showing the usage of the `envara` library
"""
# Get the application arguments without the executable (see launch settings)
args = sys.argv[1:]
# Make expansions portable betwen POSIX and Windows
chars = EnvChars.POSIX_WINDOWS if Env.IS_WINDOWS else EnvChars.POSIX
esc = chars.escape
# Expand inline and print the result
input = f"Home ${{HOME:-$USERPROFILE}}, arg {esc}#1: $1 # Line comment"
print(f"\n*** Expanded string ***\n\n{Env.expand(input, args, chars=chars)}")
# Define directory that contains all env-like files
inp_dir = Path("config")
# List of all platforms
print(f"\n*** All platforms ***\n")
print(f'"{'", "'.join(Env.get_all_platforms())}"')
# List of current platforms
print(f"\n*** Current platforms ***\n")
print(f'"{'", "'.join(Env.get_cur_platforms())}"')
# List files related to the current platform stack
print(f"\n*** Env file stack ***\n")
print(f'"{'", "'.join([x.name for x in EnvFile.get_files(inp_dir)])}"')
# Make a copy of the old environment variables
old_env = os.environ.copy()
# Place some .env files into directory below
EnvFile.load(inp_dir, args=args)
# Show new environment variables
print(f"\n*** New environment variables ***\n")
for key, val in os.environ.items():
if key not in old_env:
print(f"{key} => {val}")
return 0
###############################################################################
if __name__ == "__main__":
exit(main())
###############################################################################
Library Overview
The envara library provides the following main components:
Env class
Provides string expansions via static methods to:
- Expand environment variables and command-line arguments in strings as well as execute sub-commands
- Unquote strings (remove enclosing quotes)
- Unescape special characters (
\t,\n,\u0022, etc.) - Quote strings with proper escape handling
- Split input string into command-line arguments and apply the actions mentioned above
- Load and process stacked environment files
- Detect and work with multiple platforms (POSIX, Windows, RiscOS, VMS, etc.)
Key class variables:
IS_POSIX—Trueif running under Linux, UNIX, BSD/macOS or similarIS_RISCOS—Trueif running under Risc OSIS_VMS—Trueif running under OpenVMS or similarIS_WINDOWS—Trueif running under Windows or OS/2PLATFORM_POSIX—"posix"constantPLATFORM_WINDOWS—"windows"constantPLATFORM_THIS—sys.platform.lower()of the running systemSPECIAL— dict mapping escape-letter keys (a,b,f,n,r,t,v) to control charactersSYS_PLATFORM_MAP— regex-to-platform-list mapping driving platform detection
Platform-specific character sets (EnvChars):
EnvChars.POSIX—$expansion,\escape,'hard quote,"normal quoteEnvChars.POSIX_WINDOWS— Same asPOSIXbut with^escape (compatible with Windows paths)EnvChars.WINDOWS—%NAME%expansion,^escape,"normal quoteEnvChars.VMS—'NAME'expansion,^escape,"normal quoteEnvChars.RISCOS—<NAME>expansion,\escape,"normal quoteEnvChars.Default— OS-detected default (auto-initialized at import)EnvChars.Current— currently active character set (may differ fromDefaultafterselect())
Key methods:
EnvChars.init_default()— (re)initializeDefaultbased on the running OSEnvChars.select(cutter)— choose anEnvCharsDatavariant by matching a line-comment starter against known cutters (#,::,!,|)
Each EnvCharsData instance also exposes:
.copy_with(**overrides)— create a modified copy (used internally forPOSIX_WINDOWS).expand_len,.windup_len,.escape_len,.cutter_len,.hard_quote_len,.normal_quote_len— cached string lengths of each special character.all_quotes— combined string of normal and hard quote characters
Key static methods:
Env.expand(input, ...)— expand environment variables, arguments, escape sequences, and sub-commandsEnv.expand_path(path, ...)— expand aPath, with tilde (~) home-directory expansion viaPath.expanduser()Env.strip(input, ...)— strip leading/trailing spaces and detect surrounding quote typeEnv.unquote(input, ...)— remove enclosing quotes (single or double)Env.unescape(input, ...)— process escape sequences (\n,\t,\u0022, etc.)Env.quote(input, ...)— enclose in quotes with proper escape handlingEnv.split(input, ...)— split into command-line arguments
EnvFile class
Reads series of key=value lines from env files, removes line comments, expands environment values and arguments, expands escaped characters, and sets or updates those as environment variables. Also allows hierarchical OS-specific stacking of such files.
Key class constants:
EOF_CHAR—\x1A(Ctrl-Z) used as a file separator when concatenating multiple files. Recognized byload_from_str()to reset the platform-chars selection for the next file segment.RE_KEY_VALUE— compiled regex\s*=\s*to split each line into key and value.DEFAULT_EXPAND_FLAGS— default flags forload()andload_from_str().
Key public methods:
EnvFile.load(dir, indicator, flags, *filters)— discover and load all relevant env files from a directory, with platform-aware stackingEnvFile.load_from_str(data, args, expand_flags)— parse a string buffer ofkey=valuelines directly, with per-file platform detection viaselect_chars()EnvFile.read_text(files, flags)— read content from a list ofPathobjects, insertingEOF_CHARseparators between files, respecting the loaded-files cacheEnvFile.select_chars(input, chars)— examine the first non-whitespace character(s) of a line to determine whichEnvCharsDatato use by matching cutters (#→ POSIX,::→ Windows,!→ VMS,|→ RISC OS)EnvFile.get_files(dir, indicator, flags, *filters)— discover eligible env files for a directory with platform-based filtering
EnvFilter and EnvFilters
Environment-related filtering, mainly for use with EnvFile. Allows filtering env files based on:
- A necessary part of the filename (indicator)
- Current runtime values (e.g.,
dev,prod) - All possible values for the runtime environment
Key methods:
EnvFilter(indicator, cur_values, all_values)— constructor;cur_valuesandall_valuesaccept comma-separated strings or listsEnvFilter.has_value(name, value)— static check whether a value appears as a delimited token within a filename stringEnvFilter.search(name)— find the matching index withincur_values/all_valuesfor a given filenameEnvFilters.process(filenames, filters)— static method to filter and sort filenames according to a list ofEnvFiltercriteria (called internally byEnvFile.get_files())
Enumerations
EnvExpandFlags- Controls expansion behavior (allow shell commands, subprocess execution, skip hard-quoted strings, strip comments/spaces, unescape, unquote)EnvFileFlags- Controls file loading behavior (add platforms before/after filters, reset accumulated files)EnvPlatformFlags- Controls platform listing (add empty string for any platform)EnvQuoteType- Specifies quote type (none, hard/single-quoted, normal/double-quoted)
POSIX-style Expansions
In fact, these are bash rules, but it makes sense to apply them to the environment variable expansions on any Linux/BSD/UNIX platform. It is implemented via Env.expand(...) which eventually calls private method __expand_posix(...).
Supported constructs
Basic variable expansion:
$NAMEand${NAME}- expand variable from the provided mapping (defaults toos.environ)- Positional arguments:
$1,$2, … (1-based indices supplied viaargs) - out-of-bounds indices leave the pattern unchanged $$expands to the current process ID
Length and substrings:
${#NAME}returns the length ofNAME's value${NAME:offset[:length]}extracts a substring; negativeoffsetcounts from the end
Defaulting and alternatives:
${NAME:-word}- usewordifNAMEis unset or null${NAME-word}- usewordifNAMEis unset${NAME:+word}- usewordifNAMEis set and non-empty${NAME:?message}and${NAME?message}- raiseValueErrorwithmessageif variable is not set (or null for:?)
Assignment:
${NAME:=word}- setNAMEto the expansion ofwordifNAMEis unset or null- Assignment writes to the
varsmapping you pass (if any)
Pattern removals:
${NAME#pattern}and${NAME##pattern}- remove shortest/longest matching prefix using glob-style patterns${NAME%pattern}and${NAME%%pattern}- remove shortest/longest matching suffix
Substitution:
${NAME/pat/repl}- replace first match of globpatwithrepl(replacement is recursively expanded)${NAME//pat/repl}- replace all matches- Anchored forms:
${NAME/#pat/repl}replaces matching prefix,${NAME/%pat/repl}replaces matching suffix - Global anchored forms iteratively apply the anchored substitution
Case modification (NEW):
${var^}- uppercase first character${var^^}- uppercase all characters${var,}- lowercase first character${var,,}- lowercase all characters${var~}- toggle case of first character${var~~}- toggle case of all characters- Pattern-based forms:
${var^[pattern]},${var^^[pattern]},${var,[pattern]},${var,,[pattern]}- apply to characters matching the glob pattern - When variable is unset, the expression is returned unchanged; when null (empty), the empty string is returned
Unescaping:
- An escape character before
$prevents expansion - Pairs of backslashes reduce appropriately
- An escape character followed by
xNNconverts into an ASCII character with the hexadecimal codeNN - An escape character followed by
uNNNNconverts into a Unicode character with the hexadecimal codeNNNN - An escape character followed by
a,b,f,n,r,t,vis expanded to a bell, back, form-feed, newline, carriage-return, horizontal tab, vertical tab - All other characters, preceded by the escape one, convert to themselves
Sub-command substitution:
$(...)and`...`are supported- Inner content is first expanded before execution
- The executed sub-command's stdout (with trailing newline removed) is inserted into the result
- Timeouts and non-zero exit codes raise
ValueError
Safety and Configuration
The following parameters control execution of command substitutions:
flags:EnvExpandFlagscontrols expansionALLOW_SHELL- command substitutions executed withshell=True(less safe, more flexible)ALLOW_SUBPROC- executed withshell=Falseusingshlex.split(...)(safer)
Simple Expansions for Windows, OpenVMS, and RiscOS
This method of expansion supports:
- Windows-style
%NAME%,%1,%*,%%, and simple%~modifiers (e.g.,%~dp1) for extracting path components on Windows-like inputs. - A substring form for named variables using the syntax
%NAME:~start[,length]%- negativestartcounts from the end. - Even more limited OpenVMS-like variables expansion
'NAME'as well as the RiscOS-like<NAME>.
It is implemented via Env.expand(...) which eventually calls private method __expand_simple(...).
POSIX-style on Windows
EnvChars.POSIX_WINDOWS provides POSIX-style ($-based) expansion with the Windows caret (^) as the escape character. This avoids ambiguity between backslash path separators and the POSIX escape character when expanding paths on Windows.
Env File Lookup
The EnvFile.load(...) method looks for the following files. The leading dot is optional; <sys.platform> is lowercased; each file is loaded at most once (unless the internal cache is dropped via EnvFileFlags.RESET_ACCUMULATED).
For any filter:
[.-_]env[.-_]
Platforms (added to the list of filters by default):
| Platform | Files (not limited to) |
|---|---|
| Any platform | .env, -env, _env, env, env-, env_, .env-, .env_, -env., -env_, _env., _env- |
| Any POSIX platform | .env.posix, [.]posix.env, abc.posix-def_env (have env and posix parts) |
| Linux, Android | POSIX + .env.linux, [.]linux.env, abc.linux-def_env |
| BSD-like | POSIX + .env.bsd, [.]bsd.env, bsd_abc.def-env.ghi |
| iOS, iPadOS, macOS | BSD + .env.darwin, [.]darwin.env |
| Windows | .env.windows, [.]windows.env |
| VMS | .env.vms, [.]vms.env |
| Java | POSIX or Windows |
If a platform is not listed explicitly, it falls into the last category. None of these files is required - a file is only loaded if found and verified to be relevant to the platform you are running under.
Extra filters can also be passed - things like "dev" (runtime environment) or "es" (current language), as well as lists of all expected environments and languages.
Example (portable Chrome launcher):
# .env (or .env.any)
APP_NAME = $0
APP_VERSION = "${1}.$2.$3"
APP_FULL_NAME = "$APP_NAME-$APP_VERSION"
PROECT_PATH = ~/Projects/$APP_FULL_NAME
BROWSER_ARGS = "--opt1 arg1 --opt2 arg2"
# .env.linux (or .linux.env)
CMD_CHROME = "google-chrome $BROWSER_ARGS"
# .env.bsd (or bsd.env)
CMD_CHROME = "chrome $BROWSER_ARGS"
# .env.macos (or macos.env)
CMD_CHROME = "\"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome\" $BROWSER_ARGS"
# .env.windows
CMD_CHROME = "chrome $BROWSER_ARGS"
What Kind of Expansion to Choose in the Env Files?
By default, the expansion that is specific to the current platform will be chosen. You can override that by having the first non-empty line representing a line comment for the desired platform's rules. For instance, if the first non-empty line in an env file starts with #, it will force Env.expand(...) to use POSIX (in fact, bash) rules. If it starts with ::, then Windows, if with !, then OpenVMS, and if with |, then RiscOS rules will apply. This resembles the shebang #! sequence for Linux/BSD/UNIX shell scripts. And it is always a good idea to start such a file with a meaningful comment anyway, so you can address both needs at once.
When multiple files are loaded (via EnvFile.load()), the \x1A (EOF_CHAR) separator is inserted between them by read_text(). When load_from_str() encounters this character, it resets the platform-chars selection, allowing each file segment to independently declare its expansion rules via its first comment line.
Good Luck!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file envara-0.5.7.tar.gz.
File metadata
- Download URL: envara-0.5.7.tar.gz
- Upload date:
- Size: 37.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0b4595880bebde493414585aacbdab95296242c88c22866002cc6f371b6983de
|
|
| MD5 |
0b9d29a248af3514abc3bf68adfa07f1
|
|
| BLAKE2b-256 |
4593373501c34e05bb4591ef8a19382101fbabe87bdc0327b78de09c291e89dc
|
File details
Details for the file envara-0.5.7-py3-none-any.whl.
File metadata
- Download URL: envara-0.5.7-py3-none-any.whl
- Upload date:
- Size: 32.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb5873c7425903b2a23186a0ea8fa5e30c1229ecdb0daf47a10d63bcfc4a31a9
|
|
| MD5 |
e71c96806e276b4889e6a3e45fb427b3
|
|
| BLAKE2b-256 |
b5d1d965612949d9e3fb3e7e823d84d8b6f2dce9b4c3e324c8f9afc3f589d946
|