Run Commands with a Controlled Environment Using env
Your script works in your shell and dies in cron, so use env to run one command with exactly the variables you choose. You will set selected variables, change the working directory, or start with no inherited environment at all. The examples use GNU env from coreutils 9.4, installed here as package version 9.4-3ubuntu6.3.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell and the coreutils package, which is normally already installed. The examples only affect child processes. They do not change your shell, system configuration or service definitions, so no elevated privileges are needed.
1. Confirm the installed command
Check the binary and version before relying on an option in a script:
$ command -v env
/usr/bin/env
$ env --version | head -n 1
env (GNU coreutils) 9.4
This guide targets GNU env. Other Unix implementations can have a smaller option set, so do not copy the long options into a portable POSIX script without checking its target systems.
2. Set variables for one command
Put NAME=VALUE arguments after the env options and before the command. The child gets the assignments. Your current shell does not:
$ env APP_MODE=staging FEATURE_FLAG=1 /bin/sh -c \
'printf "APP_MODE=%s FEATURE_FLAG=%s\n" "$APP_MODE" "$FEATURE_FLAG"'
APP_MODE=staging FEATURE_FLAG=1
$ printf 'APP_MODE=%s\n' "${APP_MODE-unset}"
APP_MODE=unset
The single quotes matter. They make the inner shell expand the variables after env has started it.
Checkpoint
The final command shows your interactive shell still has no APP_MODE value. Use this pattern when a test, build or one-off administrative command needs a temporary setting.
3. Start with an empty environment
Use -i, also written --ignore-environment, when inherited variables could be hiding a dependency. Add back only the values the command needs:
$ env -i HOME=/tmp/demo PATH=/usr/bin:/bin /bin/sh -c \
'printf "HOME=%s\nPATH=%s\n" "$HOME" "$PATH"; env | wc -l'
HOME=/tmp/demo
PATH=/usr/bin:/bin
2
An empty environment is stricter than a clean login shell. It can drop variables programs normally expect, including locale and configuration paths. If a command fails after -i, add variables one at a time. Do not paste your whole environment back in, or you will never learn which variable it needed.
A lone - is another spelling for ignoring the inherited environment, but -i is easier to recognise in review.
4. Remove one inherited variable
Use -u NAME or --unset=NAME when most of the environment is useful but one value must not reach the child:
$ export DEMO_SECRET='do-not-pass-this-on'
$ env -u DEMO_SECRET /bin/sh -c \
'printf "DEMO_SECRET=%s\n" "${DEMO_SECRET-unset}"'
DEMO_SECRET=unset
$ unset DEMO_SECRET
The last unset puts this shell back as it was before the demonstration.
Warning
Treat environment variables as potentially sensitive. The child can see them, and depending on the program they may end up in diagnostics or logs. env is not a promise that a secret is erased from every process or from shell history.
5. Run from a specific directory
GNU env 9.4 supports -C DIR, or --chdir=DIR, to change the child process's working directory:
$ env -C /tmp /bin/pwd
/tmp
$ pwd
/home/alice/src/demo
The directory must exist and be accessible before the command starts. This helps when a tool has no working-directory option of its own. It does not create the directory, and it does not change the caller's directory. Check the path before using it in automation:
$ test -d /path/to/workdir && env -C /path/to/workdir /path/to/program
6. Read the exit statuses
Everything after the environment assignments belongs to the command. If the command is a shell, quote its script so the child, not the parent, expands the variables:
$ env RUN_NUMBER=42 /bin/sh -c 'printf "run=%s\n" "$RUN_NUMBER"'
run=42
When the child starts, env normally returns the child's status unchanged:
$ env /bin/sh -c 'exit 7'
$ printf 'status=%s\n' "$?"
status=7
The manual reserves three statuses for env itself:
- 125: env itself failed.
- 126: the command was found but could not be invoked.
- 127: the command could not be found.
These help a script tell its own failure from the failure of the program it launched, although a child program can technically choose any exit status itself. To see one, name a command that does not exist:
$ env /path/that/does/not/exist
env: '/path/that/does/not/exist': No such file or directory
$ printf 'status=%s\n' "$?"
status=127
The wording can vary slightly by locale and coreutils build. The status is the useful part.
7. Use -S only for shebang argument splitting
-S or --split-string splits one string into separate arguments. Its main use is a script interpreter line that needs more than one interpreter argument:
#!/usr/bin/env -S perl -w -T
Without -S, env would look for one executable whose name included the spaces.
Warning
Do not use this option to turn untrusted text into a command line. Shell quoting, variable expansion and interpreter-specific parsing can make the result surprising. Keep a shebang's arguments fixed and reviewable.
Common traps
- Mixing up
env NAME=value commandandNAME=value command. In ordinary shells both are temporary assignments. Reach for env when you need its other features, when the command is chosen dynamically, or when a shebang invokes it. - Option-looking values before the command boundary. Check them first. Use the command's documented end-of-options convention where it has one, and quote values containing spaces or shell metacharacters.
- Expecting shell syntax. env does not interpret it. Pipelines, redirections and command substitutions need a shell such as
/bin/sh -c. - Changing more than you meant to. Environment changes can alter configuration, credentials, library search paths and proxy behaviour. Before using
-i,-uor a replacementPATHin a service wrapper, test the exact command as the service account and keep the previous wrapper available for recovery.
Done means
- Implementation confirmed. The installed command is GNU coreutils env 9.4.
- Shell untouched. Temporary variables reached the child without changing the current shell.
- Right tool chosen. You can pick between setting, removing and isolating environment variables.
- Directory switched safely. You can run a child elsewhere while your shell stays where it was.
- Statuses understood. env returns the child's status, with 125, 126 and 127 reserved for its own launch failures.
- Changes reviewed. You treat environment changes and shebang parsing as reviewable configuration, not invisible magic.