15

I tried the following but it doesn't seem to work:

$ cat script.sh
#!/bin/env -i /bin/sh

/bin/env
$ script.sh
/bin/env: invalid option -- ' '
Try `/bin/env --help' for more information.
7

The reason this does not work is because it sees -i /bin/sh as a single argument to env. Usually this would be 2 arguments, -i and /bin/sh. This is just a limitation of the shebang. No way around it.


However you can still perform this task, just a different way.

If you want this task to be performed by the script itself, and not have to do something like env -i script.sh, you can have the script re-exec itself.

#!/bin/sh
[ -z "$CLEANED" ] && exec /bin/env -i CLEANED=1 /bin/sh "$0" "$@"

This will cause the script to re-exec itself if the CLEANED environment variable isn't set. Then on re-exec, it sets the variable to make sure that it doesn't go into a loop.

|improve this answer|||||
  • env from the GNU coreutils now have an option -S to workaround issues similar to this one but not exactly the same. For example #!/usr/bin/env -S perl -T. – Weijun Zhou Nov 22 '18 at 17:54
  • Just tried. It works for the original question as well. Just use #!/usr/bin/env -S -i /bin/sh. Be aware of portability issues. – Weijun Zhou Nov 22 '18 at 18:07
3

Run your script with env -i:

env -i script.sh

And the script as usual:

#!/bin/sh
# ... your code here

If you mean to run with a clean environment without explicitly say that when you run. Eduardo Ivanec gives some ideas in this answer, you can recursively call your script with exec when the environment is not clean (e.g. $HOME is defined):

[ "$HOME" != "" ] && exec -c $0
|improve this answer|||||
  • Nice. Just don't do env -i bash like I did and then wonder why your env variables are still set (of course bash resources .bashrc and friends 8) – Neil McGill Oct 14 '15 at 15:35
2

With bash, you can do it like this:

#!/usr/bin/bash

set -e
set -u

[ -v HOME ] && exec -c "$0" "$@"

# continue with the rest of the script
# e.g. print the cleaned environment:
export

The set -e and set -u commands are not strictly necessary, but I include them to demonstrate that this approach doesn't rely on accessing unset variables (as e.g. [ "$HOME" != "" ] would) and is compatible with a set -e setting.

Testing the HOME variable should be safe because bash executes scripts in non-interactive mode, i.e. configuration files like ~/.bashrc (where environment variables may be set) are not sourced during startup.

Example output:

declare -x OLDPWD
declare -x PWD="/home/juser"
declare -x SHLVL="1"
|improve this answer|||||
0

$ cat script.sh

#!/bin/env -i /bin/sh

Sadly it won't work that way — Linux does consider -i /bin/sh as one argument to be passed to env (see Shebang).

|improve this answer|||||
0

The Linux 2-argument shebang limitation (interpeter + single argument) is noted in most of the answers, but to say this cannot be done is incorrect — you just need to change to an interpreter that can do something useful with a single argument:

#!/usr/bin/perl -we%ENV=();exec "/bin/sh " . join " ", map "'$_'", @ARGV;
# your sh script here

What this does is invoke perl with a one-liner script (-e) that clears %ENV (cheaper than env -i) and invokes exec /bin/sh, correctly quoting the arguments. Further perl logic can be added, if required (though not much on Linux since you're limited to BINPRM_BUF_SIZE characters, which is likely 128)

Sadly, this is Linux specific, it will not work on a system which allows multiple shebang arguments :-/

perl processes this string as a single argument, so it's not quoted above as you would ordinarily do with perl -e ... from the command line (if you were to add quotes these are preserved, perl sees only a literal string, with warnings on it will complain about a useless constant).

Also note there's a slight change in behaviour when used this way, @ARGV normally contains only the arguments, and $0 contains the script, but with this shebang $ARGV[0] is the name of the script (and $0 is -e) which makes it a little easier.

You can also solve this with an interpreter that "reprocesses" its command line (without an extra -c argument) the ancient AT&T ksh93 does:

#!/bin/ksh /usr/bin/env -i /bin/sh

though perhaps ksh isn't as common nowadays ;-)

(bash has a similar feature with --wordexp, but it's "undocumented" in the versions where it works, and not enabled at compile time in the versions where it is documented :-/ It also can't be used for this since it needs two arguments...)

Also, effectively a variation on @Patrick and @maxschlepzig's answers:

#!/bin/bash
[ "$_" != bash ] && exec -c -a "bash" /bin/bash "$0" "$@"
# your script here

Rather than use a new variable this uses the special "_" variable, if it's not set to exactly "bash" then replace the script with exec using -a to make ARGV[0] (and hence $_) just "bash", and using -c to clear the environment.

Alternatively, if it is acceptable to clean the environment at at the start of the script (bash only):

#!/bin/sh
unset $(compgen -e)
# your script here

That uses compgen (completion helper) to list the names of all the exported environment variables, and unsets them in one go.

See also Multiple arguments in shebang for more details on the general problem of shebang behaviour.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.