Why is #!/usr/bin/env bash superior to #!/bin/bash?

BashShebang

Bash Problem Overview


I've seen in a number of places, including recommendations on this site (https://stackoverflow.com/questions/10376206/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?

Bash Solutions


Solution 1 - Bash

#!/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.

Solution 2 - Bash

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.

Solution 3 - Bash

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.

Solution 4 - Bash

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.

Solution 5 - Bash

 #!/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.

Solution 6 - Bash

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 "$@"

Solution 7 - Bash

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionspugm1r3View Question on Stackoverflow
Solution 1 - BashziggView Answer on Stackoverflow
Solution 2 - BashDavid W.View Answer on Stackoverflow
Solution 3 - BashSean PerryView Answer on Stackoverflow
Solution 4 - BashJames KoView Answer on Stackoverflow
Solution 5 - BashktaView Answer on Stackoverflow
Solution 6 - BashMihir LuthraView Answer on Stackoverflow
Solution 7 - BashgitarthaView Answer on Stackoverflow