env — set, strip, and run commands in a custom environment

Run one command with a different world around it — without touching yours.

You use it every time any script runs, and you've probably never typed it once — until today.

What it does

env runs a child command with an environment you control, then walks away — your shell's variables stay untouched. Prefix the command with NAME=VALUE pairs to add variables for that one child only, use -u NAME to yank one out, or -i to wipe the slate completely and start from nothing. No command? Plain `env` prints the whole environment, and `env | sort` gives you the readable version. It's the smallest, most portable way to hand a child a different reality for its lifetime.

Why it matters

Most tools read their configuration from environment variables: database URLs, API keys, locales, PYTHONPATH, debug flags. env lets you change exactly one of those for exactly one invocation instead of `export`-ing and un-exporting around it. When a cron job fails silently because PATH is wrong or LANG is C, `env -i` becomes an instant lab: it shows you exactly what a cold, minimal environment does to your command. It's the difference between guessing and reproducing.

Examples

env | sort
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
TERM=xterm-256color
USER=kmail
XDG_RUNTIME_DIR=/run/user/1000
_=/usr/bin/env

With no command argument, env prints every variable. Pipe through sort to read it; it's the same thing printenv does without the sorting.

env DB_HOST=db.internal APP_PORT=5433 printenv DB_HOST APP_PORT
db.internal
5433

The classic use: add variables for one child only. printenv sees them, but as soon as this line finishes they're gone — nothing leaks back into your shell. This is how you hand a Python or Node one-shot a database URL without exporting it.

echo "before:  FIZZ=${FIZZ-unset}"; FIZZ=buzz
env -u FIZZ bash -c 'echo "inside  env -u FIZZ: FIZZ=${FIZZ-unset}"'
echo "after:   FIZZ=${FIZZ-unset}"
before:  FIZZ=unset
FIZZ=buzz (exported)
inside  env -u FIZZ: FIZZ=unset
after:   FIZZ=buzz

-u removes a variable for the child only. FIZZ is unset inside the env call but still set in your shell afterward — a surgical strip for one command, no cleanup step needed.

env -i bash -c 'echo "HOME=${HOME-unset} PATH=$PATH"'
HOME=unset PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

-i wipes the entire environment and gives the child only what the program itself sets up — here bash rebuilt a minimal PATH from default compilation settings. This is the first tool for debugging cron jobs that work by hand and die on the schedule.

env PYTHONUNBUFFERED=1 python3 -c 'import os; print(os.environ.get("PYTHONUNBUFFERED"))'
1

Inject one tuning variable into a child with zero pollution: the parent never sees PYTHONUNBUFFERED after the line ends. Handy for forcing line-buffered output or flipping a library flag without editing any config file.

env LC_ALL=C.UTF-8 LANG=C.UTF-8 python3 -c 'import locale; print(locale.getlocale())'
('C', 'UTF-8')

Force a sane locale for one child that misdetects it — a classic fix for scripts that crash on non-ASCII output or sort differently depending on who launched them. Same pattern fixes date and sort behavior in tight cron environments.

Flags

FlagMeaning
NAME=VALUE COMMANDSet variables for the child only, then run COMMAND — the whole point of env; nothing is exported to your shell.
-u, --unset=NAMERemove NAME from the child's environment. Multiple -u flags stack: env -u http_proxy -u https_proxy curl ...
-i, --ignore-environmentStart the child with an empty environment. Great for reproducing minimal/cron environments; beware it also drops PATH unless you re-add it.
-C, --chdir=DIRChange the working directory before running the command (GNU coreutils ≥ 8.27). Replaces the cd ... && env pattern.
printenvNot a flag, but env's sibling: printenv NAME prints a single variable value, and printenv with no args matches env with no command. Use it to read one value without scrolling a wall of text.
-0, --nullTerminate output lines with NUL instead of newline — safe to pipe into xargs -0 when a value contains newlines.
env --helpQuick reference for every option, including the more exotic -S string-splitting and --default-signal handlers on newer coreutils.

From System V to every script on the box

env originated in Unix System V, where it let you control a command's environment without re-logging in. It stayed a small, boring, correct utility for decades — and that's exactly why it became indispensable. The -i ignore-environment and -u unset options were standardized by POSIX, so env behaves identically on macOS, Linux, and every BSD. Its unglamorous reliability is the reason you can copy a one-liner across three operating systems and trust it.

The shebang that launched a thousand tools

The most famous use is the interpreter lookup: #!/usr/bin/env python3. Instead of hardcoding one absolute path (e.g. /usr/local/bin/python3), env finds 'python3' by walking PATH — so the same script works under pyenv, virtualenv, conda, and the system install alike. This pattern is why version managers and every virtualenv-generated script work at all. env isn't just a utility here; it's the runtime resolver for a huge fraction of the ecosystem.

Fun facts

Pros

Cons

Takeaways