opscape

System and users

env

Print the environment, or run a command with a modified environment.

env [OPTION...] [-] [NAME=VALUE...] [COMMAND [ARG...]]

12 of 12 examples run in Debian 13.7 on 2026-10-31 · coreutils 9.7-3 · manual: env(1)

env has two jobs. With no command it prints the environment it was given, one NAME=value per line. With a command it runs that command with an adjusted environment: extra NAME=value pairs added, variables removed with -u, or everything cleared with -i. The change applies only to that one command; your shell is untouched.

Its second, quieter job is finding programs through PATH. env python3 runs whichever python3 comes first in PATH, which is why portable scripts start with #!/usr/bin/env bash instead of hard-coding /bin/bash. GNU env -S goes one step further and splits a single string into several arguments, the missing piece for passing options in a shebang line (see The shebang line).

For printing one variable, printenv is simpler. In bash you rarely need env to set a variable for one command, because NAME=value command does the same thing; env is what you use when there is no shell in between (shebangs, exec forms in config files) or when you want -i or -u.

Examples

Print the environment

env | sort
explain
output
COLUMNS=80
EDITOR=vi
HOME=/home/dev
LANG=C.UTF-8
LOGNAME=dev
PATH=/usr/local/bin:/usr/bin:/bin
PWD=/home/dev/demo
SHELL=/bin/bash
SHLVL=1
TERM=dumb
TZ=UTC
USER=dev
_=/usr/bin/env

Only exported variables appear. Sorting makes the list easier to scan; the order env prints in is not meaningful. PWD, SHLVL and _ are added by bash itself.

Run a command with an extra variable

env GREETING=hello bash -c 'echo "$GREETING from a child"'
echo "parent sees: ${GREETING:-nothing}"
output
hello from a child
parent sees: nothing

The variable exists only in the child process. Afterwards the shell that ran env still has no GREETING.

Do the same without env in bash

TZ=Asia/Tokyo date -d '2026-03-14 12:00 UTC' '+%H:%M %Z'
explain
output
21:00 JST

Prefixing a command with NAME=value is the shell's built-in form of env NAME=value. Here date sees a different time zone for one invocation.

Remove a variable for one command

env -u EDITOR printenv EDITOR; echo "exit: $?"
explain
output
exit: 1

-u EDITOR deletes EDITOR from the child's environment, so printenv finds nothing and exits 1.

Run a command with an empty environment

env -i bash -c 'env'
explain
output
PWD=/home/dev/demo
SHLVL=0
_=/usr/bin/env

-i starts from nothing. The child bash only has the three variables it creates for itself. This is how you test whether a script secretly depends on something in your environment.

Clear the environment but keep a minimal PATH

env -i PATH=/usr/bin:/bin HOME="$HOME" bash -c 'echo "HOME=$HOME"; command -v tar'
explain
output
HOME=/home/dev
/usr/bin/tar

After -i, the NAME=value pairs are the whole environment. Passing a sane PATH and HOME gives a predictable baseline, close to what cron or a service manager provides.

Run a command with no PATH at all

env -i ls
explain
output
README.md
access.log
data
file with spaces.txt
link-to-notes
logs
notes.txt
photos
scripts
src
todo.txt

There is no PATH in the empty environment, yet ls is found: when PATH is unset, the C library's program lookup falls back to a default of /bin:/usr/bin. Scripts run this way can still pick up different programs than your interactive shell does, so set PATH explicitly.

Run a command in another directory

env -C data ls
explain
output
config.json
fruits.txt
hosts.txt
inventory.tsv
numbers.txt
poem.txt
sales.csv
servers.json
settings.conf
users.csv
windows.txt

-C DIR changes directory before running the command, without a subshell or cd. It is a GNU coreutils option (8.28 and later).

Find a program through PATH

env python3 -c 'import sys; print(sys.executable)'
explain
output
/usr/bin/python3

env searches PATH for python3 and runs the first match. This lookup is the reason #!/usr/bin/env python3 works whether Python lives in /usr/bin or a virtualenv.

Pass an option in a shebang without -S (broken)

strict.sh
#!/usr/bin/env bash -eu
echo "unset var: $nope"
chmod +x strict.sh; ./strict.sh
explain
outputexit 127
env: ‘bash -eu’: No such file or directory
env: use -[v]S to pass options in shebang lines

Linux passes everything after the interpreter path as one argument, so env is asked to run a program literally named bash -eu, with the space in its name, and fails with 127. GNU env even prints a hint pointing at -S.

Pass options in a shebang with env -S

strict.sh
#!/usr/bin/env -S bash -eu
echo "unset var: $nope"
chmod +x strict.sh; ./strict.sh
explain
outputexit 1
./strict.sh: line 2: nope: unbound variable

-S splits the string bash -eu into words, so bash really starts with -e and -u. The unset variable is now caught and the script stops with status 1, which is the point of -u.

Print the environment NUL-separated

env -0 | tr '\0' '\n' | grep -c =
explain
output
13

-0 ends each entry with a NUL byte instead of a newline, which is the only safe separator when a value can contain newlines. Here tr turns them back to count the entries.

Common pitfalls

  • env VAR=value inside a script does not change the script's own environment; it only affects the command that follows. To set a variable for the rest of the script, use export VAR=value.

  • #!/usr/bin/env bash -e fails on Linux with No such file or directory: the kernel passes bash -e as a single argument. Use #!/usr/bin/env -S bash -e, or set options inside the script with set -e.

  • env without arguments shows the environment, not shell variables. A variable set with x=1 but never exported is invisible to env and to every child process.

  • env -i also removes LANG, HOME and PATH, so programs fall back to the C locale and default paths. Pass back what the command needs.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • env -S exists in GNU coreutils 8.30 and later and in FreeBSD and macOS env; very old Linux systems lack it.

  • -C/--chdir, -0 and the long options are GNU. macOS env has -i, -u, -S and -P altpath (search a different path for the command).

Options worth knowing

A selection, described in our own words. For the full list, see env(1) or env --help.

OptionWhat it does
-i, --ignore-environmentStart with an empty environment.
-u, --unset NAMERemove NAME from the environment.
-C, --chdir DIRChange to DIR before running the command.
-S, --split-string STRINGSplit STRING into separate arguments; meant for shebang lines.
-0, --nullEnd each printed entry with NUL instead of a newline.
-v, --debugPrint each step of processing to stderr.
--block-signal[=SIG]Block delivery of the given signals to the command.
--default-signal[=SIG]Reset handling of the given signals to the default.
--ignore-signal[=SIG]Make the command ignore the given signals.
--list-signal-handlingPrint non-default signal handling to stderr before running the command.

Related