303

I've seen in a number of places, including recommendations on this site (What is the preferred Bash shebang?), to use #!/usr/bin/env bash in preference to #!/bin/bash. I've even seen one enterprising individual suggest using #!/bin/bash was wrong and bash functionality would be lost by doing so.

All that said, I use bash in a tightly controlled test environment where every drive in circulation is essentially a clone of a single master drive. I understand the portability argument, though it is not necessarily applicable in my case. Is there any other reason to prefer #!/usr/bin/env bashover the alternatives and, assuming portability was a concern, is there any reason using it could break functionality?

Community
  • 1
  • 1
spugm1r3
  • 3,381
  • 3
  • 17
  • 18
  • 11
    It isn't necessarily better. See [this question](http://unix.stackexchange.com/q/29608/10454) and [my answer](http://unix.stackexchange.com/a/29620/10454) on unix.stackexchange.com. (I'd vote to close this as a duplicate, but I don't think you can do that across sites.) – Keith Thompson Feb 06 '14 at 20:44
  • 3
    In addition to @zigg's answer, `env` may not be located at `/usr/bin`. Shebang comments are altogether a bad idea IMHO. If your default script interpreter doesn't handle shebang comments, it is just a comment. However, if you know the script interpreter can handle shebang comments, and you know the path to bash, there is no reason not to invoke it using its absolute path unless the path is too long (unlikely), or you might possibly port the script to a system that doesn't have bash located in /bin. Then again, the caveats I previously mentioned apply in that case since it involves portability. –  Feb 06 '14 at 20:49
  • 1
    @KeithThompson, thanks for the link. Perhaps my search to an answer before posting the question was a little narrow. My take-away from all this: (1) linux/unix/posix/etc... is gray, and (2) anyone claiming to absolutely have the right answer absolutely has the right answer for their particular scenario. – spugm1r3 Feb 06 '14 at 20:59
  • 4
    The behavior of many things in POSIX/Unix is well defined. The locations are not always so clear cut. Somethings have to exist like `/etc` or `/bin/sh`. `bash` is an add-on for most Unix like systems. It is only Linux where `bash` is guaranteed to be in `/bin` and most likely also linked as `/bin/sh`. Since Linux became the modern de facto Unix for a lot of people the fact that systems other than Linux might exist has been forgotten. In my own answer below I assumed Linux because you said `bash`. A lot of the BSD boxes I have worked with did not even have it installed. – Sean Perry Feb 06 '14 at 21:26
  • 5
    @Keith - In the case of Bash (as opposed to Python in the other questions)... OpenBSD does not have a `/bin/bash`. Bash is not installed by default. If you want it, you have to `pkg install bash`. Once installed it is located at `/usr/local/bin/bash`. There is nothing installed at `/bin/bash` on OpenBSD. A shebang of `#!/bin/bash` will error, and `#!/usr/bin/env bash` will succeed. – jww May 12 '19 at 15:26
  • @jww Then you can use `#!/usr/bin/env` (with the risk of invoking some other `bash` executable in some user's `$PATH`, or you can use `#!/usr/local/bin/bash`, perhaps editing the `#!` line as you install the bash script. Neither solution is ideal. Personally, I'd use `#!/usr/local/bin/bash`. Or you could `ln -s /usr/local/bin/bash /bin/.` (as root), but I'd hesitate to mess with the system like that. – Keith Thompson May 12 '19 at 20:27
  • The "enterprising individual" doesn't say `/bin/bash` is wrong. They say `/bin/sh` is wrong (in the section about writing with bash 3.x features). It could be worded better and they clearly advocate `env`, but they seem to be talking about being aware of what features your script uses and targeting the right shell. e.g. Using env allows for bash 3.x as a custom install even on a system where bash is not installed by default or is only 2.x. – Zim Dec 22 '20 at 18:56
  • I would argue using `/usr/bin/env` is *never* right. The advice seems to be aimed at letting the *author* of the script provide some sort of "portable" shebang that will work for multiple users. The shebang, however, should be set by the *installer*, which can be told precisely which interpreter is appropriate at that time. – chepner May 21 '22 at 21:18
  • Nobody mentions security concerns with `env`. A straightforward privilege escalation is easily achievable when a suid program executes a bash script that has such a shebang; the user can simply manipulate his `PATH` and get an arbitrary `bash` executable to be executed with elevated privileges (a root shell). – Eric May 21 '22 at 22:32
  • At work place, I have found that `#!/bin/bash` is better than this `env` thing, because if `env` can't find what it looks for, the script will fail with `: No such file or directory` (that's the full error message), which is hard to understand for other people. That will happen often even with `bash`, because sometimes script files use CRLF (Windows-style line breaks) accidentally, in which case `env` looks for `"bash\r"`, which it won't find. – ddekany May 30 '22 at 15:27

8 Answers8

314

#!/usr/bin/env searches PATH for bash, and bash is not always in /bin, particularly on non-Linux systems. For example, on my OpenBSD system, it's in /usr/local/bin, since it was installed as an optional package.

If you are absolutely sure bash is in /bin and will always be, there's no harm in putting it directly in your shebang—but I'd recommend against it because scripts and programs all have lives beyond what we initially believe they will have.

Mattie
  • 20,280
  • 7
  • 36
  • 54
  • 56
    what about `env` location ? POSIX does not force it. – Julio Guerra Feb 10 '16 at 10:01
  • 3
    @JulioGuerra Much like having a `/usr/lib/sendmail` (or, more recently, `/usr/sbin/sendmail`) binary available to process mail, it's in a Unix-like system's best interest to have `/usr/bin/env` because the env shebang is such common practice. It is a de facto standard interface. – Mattie Feb 13 '16 at 14:51
  • 31
    @zigg That's so UN*X... <-: I mean, it's in their best interest to have a standard location for `env`, but somehow not to have a standard location (which can just be a soft link) for `bash`. Not to mention, why then hashbang doesn't accept just `#!bash`, and use the `PATH`, instead of we doing exactly the same with `env`. Not confusing enough for rookies, I guess. – ddekany Feb 21 '18 at 18:48
  • 3
    @JulioGuerra According to wikipedia you are true: It's not "guaranteed". Instead, it seems "more probable". Wikipedia says `This mostly works because the path /usr/bin/env is commonly used for the env utility` here https://en.wikipedia.org/wiki/Shebang_(Unix) - We must confide in `env` being there "probably" in all systems. – Xavi Montero Oct 23 '18 at 23:57
  • 1
    @zigg I've noticed one drawback to using `/usr/bin/env` on macOS (not sure about Linux): it fails to forward some kill signals to the resulting process. This can potentially cause problems... would you mind if I edit your answer to add a warning about this? – Andy Dec 11 '18 at 19:10
  • 4
    @XaviMontero: I've used a system where `env` was in `/bin`, not in `/usr/bin` (not sure which one, likely SunOS 4). These days it's very likely `/usr/bin/env` will be available, just because of the popularity of the `#!/usr/bin/env` hack. – Keith Thompson May 12 '19 at 20:29
  • 6
    @ddekany, at first I agreed with you, but then I thought about differences between `bash` and `env`. I would wager that it is much more likely for a system have or support multiple types of shells (`sh`, `csh`, `tsh`, `zsh`, `fish`, `bash`, ...) and multiple versions of shells (bash 3 vs bash 4) than it is for there to multiple `env` programs. ...Still true that knowing these differences does not lend well to beginners being able to delve in. – Kevin Jul 23 '19 at 15:10
  • "because scripts and programs all have lives beyond what we initially believe they will have." so true. so so true. – taiyodayo Jul 05 '21 at 04:00
  • @ddekany I think the reason is because `bash` is just an interpreter. One of many. It could be any other shell interpreter or `php` or `python` etc. `#!/usr/bin/env` shebang supports **any** interpreter. And it's easier to force only one program (`env`) to always be available at some path than do this for any known and unknown interpreters. – Ruslan Stelmachenko May 28 '22 at 14:19
  • Technically, using `/usr/bin/env` introduces a security vulnerability, because an attacker can add a bash executable to a location referenced in a badly composed `$PATH` (such as one where `~/bin` is before `/usr/bin`. – Marco Feb 21 '23 at 10:46
59

The standard location of bash is /bin, and I suspect that's true on all systems. However, what if you don't like that version of bash? For example, I want to use bash 4.2, but the bash on my Mac is at 3.2.5.

I could try reinstalling bash in /bin but that may be a bad idea. If I update my OS, it will be overwritten.

However, I could install bash in /usr/local/bin/bash, and setup my PATH to:

PATH="/usr/local/bin:/bin:/usr/bin:$HOME/bin"

Now, if I specify bash, I don't get the old cruddy one at /bin/bash, but the newer, shinier one at /usr/local/bin. Nice!

Except my shell scripts have that !# /bin/bash shebang. Thus, when I run my shell scripts, I get that old and lousy version of bash that doesn't even have associative arrays.

Using /usr/bin/env bash will use the version of bash found in my PATH. If I setup my PATH, so that /usr/local/bin/bash is executed, that's the bash that my scripts will use.

It's rare to see this with bash, but it is a lot more common with Perl and Python:

  • Certain Unix/Linux releases which focus on stability are sometimes way behind with the release of these two scripting languages. Not long ago, RHEL's Perl was at 5.8.8 -- an eight year old version of Perl! If someone wanted to use more modern features, you had to install your own version.
  • Programs like Perlbrew and Pythonbrew allow you to install multiple versions of these languages. They depend upon scripts that manipulate your PATH to get the version you want. Hard coding the path means I can't run my script under brew.
  • It wasn't that long ago (okay, it was long ago) that Perl and Python were not standard packages included in most Unix systems. That meant you didn't know where these two programs were installed. Was it under /bin? /usr/bin? /opt/bin? Who knows? Using #! /usr/bin/env perl meant I didn't have to know.

And Now Why You Shouldn't Use #! /usr/bin/env bash

When the path is hardcoded in the shebang, I have to run with that interpreter. Thus, #! /bin/bash forces me to use the default installed version of bash. Since bash features are very stable (try running a 2.x version of a Python script under Python 3.x) it's very unlikely that my particular BASH script will not work, and since my bash script is probably used by this system and other systems, using a non-standard version of bash may have undesired effects. It is very likely I want to make sure that the stable standard version of bash is used with my shell script. Thus, I probably want to hard code the path in my shebang.

Asclepius
  • 57,944
  • 17
  • 167
  • 143
David W.
  • 105,218
  • 39
  • 216
  • 337
  • 10
    This isn't true: "The standard location of bash is /bin," (unless you can cite a standards document) it's perhaps more accurate to that that it's the "usual" location on most Linux distributions and macos, but it's not a *standard* on unix systems in general (and is notably not the location on most of the *bsds). – tesch1 Mar 17 '19 at 17:43
  • 5
    NixOS is an example of a current Linux distribution that installs nothing (bash, included) in /bin – slackhacker Sep 27 '20 at 20:50
16

There are a lot of systems that don't have Bash in /bin, FreeBSD and OpenBSD just to name a few. If your script is meant to be portable to many different Unices, you may want to use #!/usr/bin/env bash instead of #!/bin/bash.

Note that this does not hold true for sh; for Bourne-compliant scripts I exclusively use #!/bin/sh, since I think pretty much every Unix in existence has sh in /bin.

James Ko
  • 32,215
  • 30
  • 128
  • 239
  • 1
    In Ubuntu 18.04 `/bin` dir, I see `sh -> dash`. Symbolically linked to `dash`, revealing Ubuntu's Debian nature. Run these three command strings to realize it all boils down to individual preference: `which bash` then `which sh` then `which dash`. – noobninja Nov 04 '18 at 14:29
14

For invoking bash it is a little bit of overkill. Unless you have multiple bash binaries like your own in ~/bin but that also means your code depends on $PATH having the right things in it.

It is handy for things like python though. There are wrapper scripts and environments which lead to alternative python binaries being used.

But nothing is lost by using the exact path to the binary as long as you are sure it is the binary you really want.

Sean Perry
  • 3,776
  • 1
  • 19
  • 31
2
 #!/usr/bin/env bash

is definitely better because it finds the bash executable path from your system environment variable.

Go to your Linux shell and type

env

It will print all your environment variables.

Go to your shell script and type

echo $BASH

It will print your bash path (according to the environment variable list) that you should use to build your correct shebang path in your script.

JFu
  • 3
  • 2
kta
  • 19,412
  • 7
  • 65
  • 47
2

Your question is biased because it assumes that #!/usr/bin/env bash is superior to #!/bin/bash. This assumption is not true, and here's why:

env is useful in two cases:

  1. when there are multiple versions of the interpreter that are incompatible.
    For example python 2/3, perl 4/5, or php 5/7
  2. when the location depends on the PATH, for instance with a python virtual environment.

But bash doesn't fall under any of these two cases because:

  1. bash is quite stable, especially on modern systems like Linux and BSD which form the vast majority of bash installations.
  2. there's typically only one version of bash installed under /bin.
    This has been the case for the past 20+ years, only very old unices (that nobody uses any longer) had a different location.

Consequently going through the PATH variable via /usr/bin/env is not useful for bash.

Add to these three good resons to use #!/bin/bash:

  1. for system scripts (when not using sh) for which the PATH variable may not contain /bin.
    For example cron defaults to a very strict PATH of /usr/bin:/bin which is fine, sure, but other context/environments may not include /bin for some peculiar reason.
  2. when the user screwed-up his PATH, which is very common with beginners.
  3. for security when for example you're calling a suid program that invokes a bash script. You don't want the interpreter to be found via the PATH variable which is entirely under the user's control!

Finally, one could argue that there is one legitimate use case of env to spawn bash: when one needs to pass extra environment variables to the interpreter using #!/usr/bin/env -S VAR=value bash.
But this is not a thing with bash because when you're in control of the shebang, you're also in control of the whole script, so just add VAR=value inside the script instead and avoid the aforementioned problems introduced by env with bash scripts.

Eric
  • 1,138
  • 11
  • 24
0

I would prefer wrapping the main program in a script like below to check all bash available on system. Better to have more control on the version it uses.

#! /usr/bin/env bash

# This script just chooses the appropriate bash
# installed in system and executes testcode.main

readonly DESIRED_VERSION="5"

declare all_bash_installed_on_this_system
declare bash

if [ "${BASH_VERSINFO}" -ne "${DESIRED_VERSION}" ]
then
    found=0

    all_bash_installed_on_this_system="$(\
        awk -F'/' '$NF == "bash"{print}' "/etc/shells"\
        )"

    for bash in $all_bash_installed_on_this_system
    do
        versinfo="$( $bash -c 'echo ${BASH_VERSINFO}' )"
        [ "${versinfo}" -eq "${DESIRED_VERSION}" ] && { found=1 ; break;}
    done
    if [ "${found}" -ne 1 ]
    then
        echo "${DESIRED_VERSION} not available"
        exit 1
    fi
fi

$bash main_program "$@"
Mihir Luthra
  • 6,059
  • 3
  • 14
  • 39
0

Normally #!path/to/command will trigger bash to prepend the command path to the invoking script when executed. Example,

# file.sh
#!/usr/bin/bash
echo hi

./file.sh will start a new process and the script will get executed like /bin/bash ./file.sh

Now

# file.sh
#!/usr/bin/env bash
echo hi

will get executed as /usr/bin/env bash ./file.sh which quoting from the man page of env describes it as:

env - run a program in a modified environment

So env will look for the command bash in its PATH environment variable and execute in a separate environment where the environment values can be passed to env like NAME=VALUE pair.

You can test this with other scripts using different interpreters like python, etc.

#!/usr/bin/env python
# python commands
gitartha
  • 95
  • 9