1522

Is there any Bash shebang objectively better than the others for most uses?

  • #!/usr/bin/env bash
  • #!/bin/bash
  • #!/bin/sh
  • #!/bin/sh -
  • etc

I vaguely recall a long time ago hearing that adding a dash to the end prevents someone passing a command to your script, but can’t find any details on that.

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
bgibson
  • 17,379
  • 8
  • 29
  • 45
  • 12
    And its `/usr/local/bin/bash` on OpenBSD. – jww Mar 05 '16 at 01:47
  • 5
    Adding the dash is meant to prevent a certain kind of setuid root spoofing attacks, see https://security.stackexchange.com/questions/45490/what-is-setuid-based-script-root-spoofing?newreg=e1ab392777f44bd8bf57e33def375cf4 – Vladislav Ivanishin Aug 16 '21 at 16:04
  • 8
    I would upvote this, but it has a score of 1337 and I don't want to disturb it! – Thomas G Henry LLC Feb 03 '22 at 18:24
  • 2
    `#!/usr/bin/env bash` poses a privilege escalation security threat 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 run instead, with elevated privileges. – Eric May 21 '22 at 22:38
  • Stumbled upon a related question: https://stackoverflow.com/questions/21612980/why-is-usr-bin-env-bash-superior-to-bin-bash – Jens Sep 17 '22 at 20:47
  • Note that the answer is very dependent on whether or not your script actually needs to be a *bash* script (relying on `bash` extensions) or if any (POSIX) `sh` would do. – mtraceur Dec 27 '22 at 13:17

7 Answers7

1979

You should use #!/usr/bin/env bash for portability: Different *nixes put bash in different places, and using /usr/bin/env is a workaround to run the first bash found on the PATH.

And sh is not bash.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
l0b0
  • 55,365
  • 30
  • 138
  • 223
  • 12
    Thanks. Also looks like adding - to the end of $!/usr/bin/env bash - won't do anything since only one argument is allowed by *nix in the shebang, and that is used by 'bash'. That's apparently only useful for preventing malicious arguments being passed to the script on the commandline if the script's shebang is one of the others with no arguments (`/bin/sh`, etc). – bgibson May 03 '12 at 02:23
  • 7
    Why not `#!/bin/bash`? Does this depend on whether the user has correct `path` defined? – Ray Shan Apr 14 '14 at 14:40
  • 16
    @Ray `bash` doesn't live in `/bin` on all systems. – ptierno Jun 01 '14 at 07:33
  • ^^ Windows Git Bash path is */user/bin/bash*, but my Siteground hosting path is */bin/bash* (checked with `echo $SHELL`). – Leo Jun 01 '16 at 20:08
  • 21
    Same for me, I just added it to an alias: `alias shebang='echo "#!/usr/bin/env bash"'`, now I just have to open the terminal and type shebang instead of going here. – Oylex Mar 01 '17 at 16:35
  • 32
    This answer is deceptive. POSIX does not say that `env` is at `/usr/bin/env`. It could be at `/bin/env` or anywhere in fact, as long as it is in the path. It could be at `/dummy/env` if `/dummy` is in `PATH`. Shebang itself is undefined under POSIX, so I could make `#!stop toaster` start the USB coffee machine and be POSIX compliant. So `#!/usr/bin/env bash` isn't particularly better than `#!/bin/bash`, it could be less portable depending. – darkfeline Mar 08 '18 at 08:05
  • 42
    @darkfeline Portability isn't absolute - it is mathematically impossible to make any script that will do the same thing on every platform. As of 2012 through 2018 `/usr/bin/env` exists on more machines than either of `/bin/bash` xor `/usr/bin/bash`, so a script that starts with this line will *do the expected thing on as many machines as possible.* – l0b0 Mar 08 '18 at 20:28
  • @I0b0 It will do the expected thing *if I have the right version in my path*. I can't specify that my script needs `bash` 4.2 or later using a shebang; I can only document that requirement. Really, the script writer shouldn't be the one in control of the shebang; it should be the person *running* the script. Python does this well, IMO: scripts use `#!python`, and the installer replaces it with the correct shebang for the target system. – chepner Mar 12 '18 at 17:48
  • 3
    @l0b0 Actually, portability can be absolute under POSIX. Assuming of course that Bash is in the path, you can use a script like [this](https://gist.github.com/darkfeline/19a91aa9e59259bb61e4614a32091600). An executable file without a shebang and not matching an binary executable format is run with sh. This is fully portable under POSIX, unlike this answer. (Sorry for double comment, stupid StackOverflow comment editing.) – darkfeline Jul 02 '18 at 08:34
  • @chepner The shebang line is IMO the wrong place to ensure that you're running a specific version; that's easily done by checking `BASH_VERSINFO`. – l0b0 Mar 08 '20 at 00:25
  • @Oylex Thanks for the awesome idea :-) I slightly changed it to copy to macos paste-board `alias shebang='echo "#!/usr/bin/env bash" | pbcopy'` – refik Jan 20 '21 at 13:50
  • @darkfeline That’s a cute trick but it *still* doesn’t do “the right thing”. It will execute *a* bash but not necessarily the *desired* bash. Case in point, on macOS `/bin/sh` is bash, but it’s a woefully outdated bash, and power users usually install a modern bash into a different location. But your script will always run `/bin/sh` on macOS, never the user’s preferred bash. By contrast, this answer works just fine on macOS. – Konrad Rudolph Jul 02 '21 at 09:00
  • @KonradRudolph if you're referring to [this](https://gist.github.com/darkfeline/19a91aa9e59259bb61e4614a32091600), then assuming that `bash` in your `PATH` is the right Bash, yes, it will run Bash. If it's not the right Bash, then `#!/usr/bin/env bash` won't work either. It sounds like you didn't see the `exec bash` line, which naturally execs bash, not `/bin/sh`. – darkfeline Jul 04 '21 at 05:05
  • @darkfeline No, it won’t run the right bash because the `if` branch will never be taken, because `BASH_VERSION` is set by `/bin/sh` on macOS (which is what will run this script if you execute it in a shell or via a system call). As a consequence, the script will never `exec bash`. – Konrad Rudolph Jul 04 '21 at 13:13
  • 1
    Then you can just check the `BASH_VERSION` to make sure you're not using the super old version? – darkfeline Jul 06 '21 at 21:39
  • @Eric The only security concern with `#!/usr/bin/env bash` is if the user is able to invoke the script with something like `-i` as the zeroth argument, and they're otherwise successfully disallowed from starting an interactive shell. But script files themselves should never have the SUID bit set anyway (because that has other inherent insecurities), and once we exclude those, the security concern is approximately zero (if you are already thoroughly preventing a piece of code from doing something as the user it already runs as, it can be prevented from doing (that thing through) `bash -i` too). – mtraceur Dec 27 '22 at 15:47
  • 1
    @mtraceur you're saying that if security measures are applied then the system is secure, so we should never care about those cases. Well you're wrong: good security practices precisely account for cases where someone made a mistake. For example why do we even have administrator privilege if all that is needed is for normal users to know he should never run some commands that may give him access to private information or gain control of the system? – Eric Jan 09 '23 at 11:29
  • @Eric That's not what I'm saying, and I'm not wrong. `#!/usr/bin/env` is *not* insecure in any real way, because any configuration where eliminating it raises security is necessarily already *so utterly insecure* that it is still utterly insecure even without `#!/usr/bin/env`. At the same time, `#!/usr/bin/env` is extremely useful for certain goals, so your vague claim of "security concerns" (vague to the point that it is indistinguishable from cargo-culting/superstition) is likely to do more real harm than `#!/usr/bin/env` ever will. – mtraceur Jan 09 '23 at 18:26
  • On further thought, I do see a real security concern: if a `#!/usr/bin/env` script is invoked with elevated privileges with an unsanitized `PATH`. Of course, SUID/SGID shouldn't be set on scripts for other reasons, SUID/SGID on scripts is deliberately disabled by default on most modern systems, and SUID/SGID programs should be sanitizing/resetting `PATH` if they're going to use it to invoke other programs. But yes, if you have a secure script which is SUID/SGID or is called by an SUID/SGID program which never calls programs through the `PATH` it inherits, then `#!/usr/bin/env` adds insecurity. – mtraceur Jan 09 '23 at 19:57
  • yes indeed @mtraceur, `/usr/bin/env` adds a level of indirection which is certainly convenient, but can also be exploited if the right -- albeit rare -- conditions are also met. – Eric Jan 17 '23 at 19:46
  • @l0b0 if env must always be in the system path, then can you just use `#!env bash`? – bgibson Aug 20 '23 at 21:38
  • 1
    @bgibson Interesting question, but no, the first command must be an absolute path. Otherwise we wouldn't need this workaround, and the shebang line could just have been `#!bash`. – l0b0 Aug 20 '23 at 21:57
218

On most but not all systems, I recommend using:

#!/bin/bash

It's not 100% portable (some systems place bash in a location other than /bin), but the fact that a lot of existing scripts use #!/bin/bash pressures various operating systems to make /bin/bash at least a symlink to the main location.

The alternative of:

#!/usr/bin/env bash

has been suggested -- but there's no guarantee that the env command is in /usr/bin (and I've used systems where it isn't). Furthermore, this form will use the first instance of bash in the current user's $PATH, which might not be a suitable version of the Bash shell.

(But /usr/bin/env should work on any reasonably modern system, either because env is in /usr/bin or because the system does something to make it work. The system I referred to above was SunOS 4, which I probably haven't used in about 25 years.)

If you need a script to run on a system that doesn't have /bin/bash, you can modify the script to point to the correct location (that's admittedly inconvenient).

I've discussed the tradeoffs in greater depth in my answer to this question.

A somewhat obscure update: One system I use, Termux, a desktop-Linux-like layer that runs under Android, doesn't have /bin/bash (bash is /data/data/com.termux/files/usr/bin/bash) -- but it has special handling to support #!/bin/bash.

UPDATE: As Edward L. points out in a comment, bash is not part of the "base OS" on FreeBSD, and even if it's installed by default, it probably won't be installed as /bin/bash. On such a system, you can either use the #!/usr/bin/env trick (I'm assuming that FreeBSD installed env as /usr/bin/env), or you can use the path where bash is installed (apparently that's #!/usr/local/bin/bash). If your scripts are only intended to run under FreeBSD, you can use #!/usr/local/bin/bash. If they're meant to be portable, you can use the #!/usr/bin/env trick (which has some disadvantages; see my answer on cited above) or you can update the #! line when you install your scripts.

There may well be similar issues on some other operating systems.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 16
    2-years later and this is still the best advice here. If the simple solution doesn't work then you've got to question your earlier decisions. The accepted and most upvoted answer isn't wrong, it's just not right :) – Software Engineer Oct 03 '19 at 23:14
  • Termux has special handling for `#/use/bin/env` too, right? – mtraceur Dec 27 '22 at 13:14
  • @mtraceur Yes, it does -- and I just discovered that the special handling works on the command line, not just on a `#!` line. – Keith Thompson Dec 27 '22 at 23:29
92

/bin/sh is usually a link to the system's default shell, which is often bash but on, e.g., Debian systems is the lighter weight dash. Either way, the original Bourne shell is sh, so if your script uses some bash (2nd generation, "Bourne Again sh") specific features ([[ ]] tests, arrays, various sugary things, etc.), then you should be more specific and use the later. This way, on systems where bash is not installed, your script won't run. I understand there may be an exciting trilogy of films about this evolution...but that could be hearsay.

Also note that when evoked as sh, bash to some extent behaves as POSIX standard sh (see also the GNU docs about this).

CodeClown42
  • 11,194
  • 1
  • 32
  • 67
  • 3
    The Public Domain Korn Shell (pdksh) is default on OpenBSD. – jww Mar 05 '16 at 01:52
  • 2
    Most systems will *not* link `/bin/sh` to anywhere in `/usr` as that would make it rather hard for the init scripts to run before `/usr` is mounted. – aij Sep 20 '18 at 01:48
  • 1
    @aij I don't know why I put "many or most" there -- I'm a fedora user, where `/bin` and `/sbin` for years have just been symlinks by default, to `/usr/bin` and `/usr/sbin`, so in that context `/bin/sh` is a link to `bash` and the actual directory is `/usr/bin`. But I'll correct the above. – CodeClown42 Sep 20 '18 at 11:03
37

Using a shebang line to invoke the appropriate interpreter is not just for BASH. You can use the shebang for any interpreted language on your system such as Perl, Python, PHP (CLI) and many others. By the way, the shebang

#!/bin/sh -

(it can also be two dashes, i.e. --) ends bash options everything after will be treated as filenames and arguments.

Using the env command makes your script portable and allows you to setup custom environments for your script hence portable scripts should use

#!/usr/bin/env bash

Or for whatever the language such as for Perl

#!/usr/bin/env perl

Be sure to look at the man pages for bash:

man bash

and env:

man env

Note: On Debian and Debian-based systems, like Ubuntu, sh is linked to dash not bash. As all system scripts use sh. This allows bash to grow and the system to stay stable, according to Debian.

Also, to keep invocation *nix like I never use file extensions on shebang invoked scripts, as you cannot omit the extension on invocation on executables as you can on Windows. The file command can identify it as a script.

nbro
  • 15,395
  • 32
  • 113
  • 196
5

It really depends on how you write your bash scripts. If your /bin/sh is symlinked to bash, when bash is invoked as sh, some features are unavailable.

If you want bash-specific, non-POSIX features, use #!/bin/bash

Nathan Smith
  • 683
  • 1
  • 10
  • 24
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
2

If portable includes "runs on alpine linux" which does not have bash installed, use #!/bin/sh, and do not specify bash explicitly. And keep your scripts POSIX compatible instead of depending on bash extensions. The number of places that now run alpine linux vastly outnumbers the rare weird systems where #!/bin/sh does not work.

On ubuntu and on alpine, this will run on some variation of ash. Which also means that you are less likely to be affected by bash-related security vulnerabilities such as shellshock than if you were running on top of bash. Bash is widely used but its internal implementation is terrible, and explicitly specifying it as the shell used instead of relying on the system to pick the shell is bad practice.

saolof
  • 1,097
  • 1
  • 15
  • 12
0

#!/bin/sh

as most scripts do not need specific bash feature and should be written for sh.

Also, this makes scripts work on the BSDs, which do not have bash per default.

weberjn
  • 1,840
  • 20
  • 24
  • 7
    But the question is what to use for Bash scripts specifically. This has the distinct and potentially serious drawback that it will not work for Bash scripts (i.e. anything which uses Bash-only features). This is a common pitfall for newbies. See also [Difference between `sh` and `bash`](https://stackoverflow.com/questions/5725296/difference-between-sh-and-bash) – tripleee Aug 31 '21 at 12:06
  • @Eric Yes, but as *@tripleee* says, it's not a good choice for Bash scripts because the original Bourne shell is missing the features that Bash added. – BadHorsie Mar 03 '23 at 10:10
  • ...which are not available on systems which do not have Bash available. Which includes the BSDs, but also anything based on busybox, which is a large chunk of anything that runs in a container, both for container size, and for security reasons. – saolof Apr 11 '23 at 15:02