Unset an environment variable for one command

Other than with the -i option, which wipes the whole environment, POSIX env doesn't provide any way to unset a variable.

However, with a few env implementations (including GNU's, busybox' and FreeBSD's at least), you can do:

env -u ENV_VAR command

Which would work at removing every instance of an ENV_VAR variable from the environment (note though that it doesn't work for the environment variable with an empty name (env -u '' either gives an error or is ineffective depending on the implementation even though all accept env '=value', probably a limitation incurred by the unsetenv() C function which POSIX requires to return an error for the empty string, while there's no such limitation for putenv())).

Portably (in POSIX shells), you can do:

(unset -v ENV_VAR; exec command)

(note that with some shells, using exec can change which command is run: runs the one in the filesystem instead of a function or builtin for instance (and would bypass alias expansion obviously), like env above. You'd want to omit it in those cases).

But that won't work for environment variables that have a name that is not mappable to a shell variable (note that some shells like mksh would strip those variables from the environment on startup anyway), or variables that have been marked read-only.

-v is for the Bourne shell and bash whose unset without -v could unset a ENV_VAR function if there was no variable by that name. Most other shells wouldn't unset functions unless you pass the -f option. (unlikely to make a difference in practice).

(Also beware of the bug/misfeature of bash/mksh/yash whose unset, under some circumstance may not unset the variable, but reveal the variable in an outer scope)

If perl is available, you could do:

perl -e 'delete $ENV{shift@ARGV}; exec @ARGV or die$!' ENV_VAR command

Which will work even for the environment variable with an empty name.

Now all those won't work if you want to change an environment variable for a builtin or function and don't want them to run in a subshell, like in bash:

env -u LANG printf -v var %.3f 1.2 # would run /usr/bin/printf instead
(unset -v LANG; printf -v var %.3f 1.2) # changes to $var lost afterwards

(here unsetting LANG as a misguided approach at making sure . is used and understood as the decimal separator. It would be better to use LC_ALL=C printf... for that)

With some shells, you can instead create a local scope for the variable using a function:

without() {
  local "$1" # $1 variable declared as initially unset in bash¹
  shift
  "$@"
}
without LANG printf -v var %.3f 1.2

With zsh, you can also use an anonymous function:

(){local ENV_VAR; command}

That approach won't work in some shells (like the ones based on the Almquist shell), whose local don't declare the variable as initially unset (but inherit the value and attributes). In those, you can do local ENV_VAR; unset ENV_VAR, but don't do that in mksh or yash (typeset instead of local for that one) as that wouldn't work, the unset would only be cancelling the local.

¹ Also beware that in bash, the local ENV_VAR (even though unset) would retain the export attribute. So if command was a function that assigns a value to ENV_VAR, the variable would be available in the environment of commands called afterwards. unset ENV_VAR would clear that attribute. Or you could use local +x ENV_VAR which would also ensure a clean slate (unless that variable has been declared read-only, but then there's nothing you can do about it in bash).


There’s no direct equivalent as far as I’m aware; you can use a subshell:

(unset ENV_VAR; exec command)