Skip to content
Shell & TerminalDeep Dive Published Updated 7 min readViews unavailable

env in Shell Scripts: Construct a Child Environment Deliberately

Use env to launch a utility with explicit inherited variables, assignments, or a cleared environment without confusing it with shell scope.

The env utility can inspect or modify the environment passed to a child program. It is useful for reproducible builds, testing configuration precedence, launching tools with a controlled locale, and writing portable shebangs. It does not change the parent shell’s variables, and it does not create a security sandbox. Environment state is only one part of process behavior: the working directory, open descriptors, credentials, umask, filesystem, and executable search path still matter.

A shell assignment such as MODE=test command applies to the environment of that command in common shells, while export MODE changes the exported state for later commands in the current shell. env MODE=test command expresses a child-process invocation through a utility. These forms can be similar for simple variables but differ around parsing, command resolution, and shell builtins. Do not use env to run a shell builtin such as cd; it launches an executable utility, not a command in the current shell execution environment.

Environment assignments are literal operands

An operand of the form NAME=value sets a variable for the invoked program. The shell performs its normal expansions before env receives that operand, so quoting remains essential:

env APP_MODE=production API_ENDPOINT="$endpoint" ./deploy-tool --check

The value can contain spaces if quoted, and an empty assignment is distinct from an unset variable. A variable not named in the command is inherited from the parent environment unless the environment is cleared. This means a command that appears to specify only two values can still receive credentials, proxy settings, locale, PATH, and tool-specific configuration from its caller.

GNU env -i starts with an empty environment before applying any listed assignments. This is useful for testing what a program truly requires:

env -i PATH=/usr/bin:/bin LC_ALL=C HOME="$HOME" ./build-tool --verify

This intentionally preserves only a small explicit set. But an empty environment can remove required variables such as HOME, TMPDIR, certificate paths, or a platform-specific runtime setting. PATH must be adequate to find both the target and any child tools it invokes. Build a policy from observed requirements, then test it in a clean container or CI job rather than assuming that every application behaves identically.

GNU env -u NAME unsets selected variables; option availability should be verified for each target implementation. Ordering between options and assignments can matter. Prefix an end-of-options marker where supported when a command name could be confused with an option, and remember that env interprets initial NAME=value operands as environment updates. Use an explicit path or a carefully controlled PATH when executable identity matters.

PATH changes affect command lookup

When env sets PATH before a command operand, that value is used to locate the executable. A test such as env PATH=/safe/bin tool therefore searches /safe/bin, while a relative or empty path entry may allow the current directory to participate. Do not construct a “clean” path by blindly preserving . or a writable directory. For privileged or reproducible work, use trusted absolute executable paths and a minimal directory list with appropriate ownership.

There is also a distinction between invoking an external program and invoking a shell command. A shell function or alias is not an executable file that env can find. env tool is useful to bypass an alias/function in some contexts by executing an external utility, but command lookup still uses PATH; it does not prove which binary will be selected unless that path is controlled. Inspect the resolved executable under the same user and environment as production.

Environment is not a complete hermetic boundary

Even with env -i, the process inherits or observes state beyond the variable list. The current working directory affects relative paths. File descriptors may carry secrets or writable access. A process can inspect mounted files, system configuration, network services, locale defaults, and kernel behavior. A build that requires strong reproducibility needs isolated inputs and toolchains, not only a filtered environment. A process that needs security isolation requires operating-system controls such as a sandbox, container, or restricted service account.

Environment variables are also a common secret channel. env with no command prints variable values, which can expose tokens in terminal scrollback or logs. Avoid dumping the full environment into CI output. Redact values before diagnostics, pass secrets through protected mechanisms, and remember that child processes can inherit them even if the immediate command never uses them. Environment variables are not encrypted storage.

Shebangs and argument boundaries

#!/usr/bin/env interpreter asks the host to locate an interpreter through PATH, which helps with installations outside a fixed path but makes the path search part of script execution. A malicious or unexpected PATH can select a different interpreter. The kernel’s shebang handling also constrains how many arguments are passed; GNU env -S enables multiword interpreter arguments on supported systems, but it is not universally portable. Validate the exact OS and env implementation before using it in a distributed script header.

For debugging, print only the variables that are relevant and compare the invoking shell’s environment with the child’s observed state. Test unset, empty, unusual values, duplicate assignments, hostile PATH entries, and values containing spaces or glob characters. The goal is a declared process contract: which values are inherited, which are overridden, which are absent, and how the executable is located.

Shebang parsing is a separate environment boundary

The kernel does not run a shell to parse every shebang. It passes an interpreter path and a constrained optional argument according to the platform’s execution rules. #!/usr/bin/env python3 delegates interpreter lookup to PATH; that improves relocatability but makes PATH part of the script’s trust model. If an attacker controls an earlier path entry, the script can run an unintended interpreter. System services should supply a known environment and use trusted directories, or use a fixed interpreter path where deployment guarantees it.

GNU env -S splits a shebang argument string and permits multiple interpreter options, but the split grammar is env-specific and not universal. Linux kernels commonly impose shebang line limits, and operating systems differ in how optional interpreter text is passed. Validate the script by executing it directly on the target OS, not just by invoking the interpreter manually. Running python script.py can succeed even when the installed shebang is broken.

Reproducibility tests and troubleshooting

Capture the actual child environment inside the same launcher context that production uses. A command run in an interactive terminal inherits shell startup settings; cron, CI, systemd, SSH, and container entrypoints each construct different environments. env -i is a useful diagnostic to remove accidental dependencies, but reintroduce required variables one at a time and write them down. Do not conclude that an environment is hermetic just because it contains few variables.

For debugging, print selected variable names and safe redacted values to stderr. env with no arguments can disclose secrets and should not be used as a routine log line. Test empty values separately from missing values because many programs distinguish them. Test a value containing spaces and wildcard characters to verify that it remains one argument. Finally, assert the executable path and version so a clean variable set does not still resolve to an unexpected tool.

Make wrappers transparent

An env wrapper can obscure the original command in logs or process listings, and its status should be propagated unchanged unless the wrapper deliberately translates it. If the only purpose is to add a few variables, document that in the job definition and preserve the target’s arguments as separate words. Avoid constructing a string and passing it to sh -c; that adds another parse and can turn data into shell syntax. When a shell is genuinely needed, quote and validate every input at that boundary.

Configuration should also identify which values are defaults and which are required secrets. env -i is not safe if an application then starts with an empty PATH and accidentally selects a fallback or fails in a confusing way. Use explicit assignments for critical locale, timezone, certificate, and temp-directory settings; let the credential manager provide secret material through its supported mechanism. A good test records variable names and provenance without copying secret values into source control or logs.

Related:

Sources:

Comments