System and users
env
Print the environment, or run a command with a modified environment.
env [OPTION...] [-] [NAME=VALUE...] [COMMAND [ARG...]]
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
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}"
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'
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: $?"
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'
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'
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
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
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)'
/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)
#!/usr/bin/env bash -eu
echo "unset var: $nope"
chmod +x strict.sh; ./strict.sh
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
#!/usr/bin/env -S bash -eu
echo "unset var: $nope"
chmod +x strict.sh; ./strict.sh
./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 =
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=valueinside 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, useexport VAR=value.#!/usr/bin/env bash -efails on Linux withNo such file or directory: the kernel passesbash -eas a single argument. Use#!/usr/bin/env -S bash -e, or set options inside the script withset -e.envwithout arguments shows the environment, not shell variables. A variable set withx=1but never exported is invisible toenvand to every child process.env -ialso removesLANG,HOMEandPATH, 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 -Sexists in GNU coreutils 8.30 and later and in FreeBSD and macOSenv; very old Linux systems lack it.-C/--chdir,-0and the long options are GNU. macOSenvhas-i,-u,-Sand-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.
| Option | What it does |
|---|---|
-i, --ignore-environment | Start with an empty environment. |
-u, --unset NAME | Remove NAME from the environment. |
-C, --chdir DIR | Change to DIR before running the command. |
-S, --split-string STRING | Split STRING into separate arguments; meant for shebang lines. |
-0, --null | End each printed entry with NUL instead of a newline. |
-v, --debug | Print 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-handling | Print non-default signal handling to stderr before running the command. |
Related
printenvPrint all environment variables, or the values of the ones you name.exportMark shell variables (or bash functions) to be passed to every program the shell starts.setTurn shell options such as -e, -u, -x and pipefail on or off, and replace the positional parameters.unsetRemove shell variables, array elements or functions, so they no longer exist rather than being empty.execReplace the shell with another program, or, with only redirections, change where the shell's own input and output go.- The shebang lineWhat the kernel does with #!, why `#!/usr/bin/env bash` and `#!/bin/bash` both exist, and the ways a shebang line breaks.
- Environment variablesHow export passes variables to child processes, the one-command NAME=value prefix, loading .env files, and why a child cannot change its parent.
- VariablesAssigning and reading shell variables, the no-spaces rule, braces, unset vs empty, scope, readonly, and the special variables bash sets for you.