Use #!/bin/sh or #!/bin/bash for Ubuntu-OSX compatibility and ease of use & POSIX

17,955

Solution 1

For starters, if you can make the assumption that Bash is preinstalled (which, to my knowledge is the case on all the systems you list), use the following hashbang to be compatible:

#!/usr/bin/env bash

this invokes whatever bash happens to be configured, no matter whether it's in /bin or /usr/local/bin.

While on most systems across a wide range (including AIX, Solaris, several BSD flavors), bash ended up in different locations, env always ended up in /usr/bin/env. The trick, however, is not mine but from the author of the Bash Cookbook.

Anyway, yes Bash would allow you to use some "modern" features that make your life easier.

For example the double brackets:

[[ -f "/etc/debian_version" ]] && echo "This is a Debian flavor"

whereas in traditional shell dialects you'd have to resort to:

test -f "/etc/debian_version" && echo "This is a Debian flavor"

but the best about the double brackets is that they allow regular expressions for matching. The Bash Hackers Wiki will give you many tricks in that direction.

You can also use quite convenient expressions like $((2**10)) or other arithmetic expressions inline with the $((expression)) syntax.

Using backticks for subshells is fine, albeit a bit outdated. But the nesting capabilities of $(command ...) invocations are way more convenient as you won't have to escape many things at different subshell levels.

These are but a few things Bash gives you over the traditional common POSIX sh syntax.

But if you want more power on the shell (not just in scripts), also have a look at zsh.

Solution 2

In Debian and Ubuntu, /bin/sh is dash, which is a POSIX-compliant shell. If you specify #!/bin/sh, you must limit yourself to POSIX statements in your script. (The advantage being that dash starts faster than bash, so your script can get its job done in less time.)

On many (most?) other Linux systems, /bin/sh is bash, which is why many scripts are written with #!/bin/sh as their shebang line even though they use bash extensions.

If you want to use bash extensions, the safest approach on all systems is to specify #!/bin/bash; that way you're explicitly stating your dependency on bash. You need to do this on Debian and Ubuntu. As an added bonus, when started as /bin/sh bash de-activates some extensions (see the description of bash POSIX mode for details); so specifying #!/bin/bash is necessary to get the full benefit of bash.

On OS X /bin/bash is available too, and /bin/sh is bash. Specifying #!/bin/bash will work fine there as well.

Solution 3

Yes, both OSX and Linux will come with /bin/bash. You should be perfectly safe. However, that is not POSIX. The POSIX shell is at /bin/sh on most (all?) systems and that is the most portable approach and the only way to be POSIX compatible.

Note that while on many systems /bin/sh points to bash, on others it can point to different shells. It's a symlink to dash on Debian and Ubuntu for example. Also, even if /bin/sh is a link to bash, the behavior of the shell changes when it is called as sh (from man bash, emphasis mine):

If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well. When invoked as an interac‐ tive login shell, or a non-interactive shell with the --login option, it first attempts to read and execute commands from /etc/profile and ~/.profile, in that order. The --noprofile option may be used to inhibit this behavior. When invoked as an interactive shell with the name sh, bash looks for the variable ENV, expands its value if it is
defined, and uses the expanded value as the name of a file to read and execute. Since a shell invoked as sh does not attempt to read and execute commands from any other startup files, the --rcfile option has no effect. A non-interactive shell invoked with the name sh does not attempt to read any other startup files. When invoked as sh, bash enters posix mode after the startup files are read.

Solution 4

If compatibility with "all Unix systems" is an absolute requirement -- and if it isn't, why are you writing a shell script? -- then, yes, you should be using #! /bin/sh, because Bash is not guaranteed to be installed anywhere, let alone in /bin.

It's actually much, much worse than that. If you need compatibility to all Unix systems, that includes things like Solaris and AIX that froze their shell environments circa 1995. Which means you have to use things like the old-fashioned sort +N syntax -- that newer systems have dropped! And it also means no shell functions, no arrays, no [[ ... ]], no ${foo#glob}, no $(( ... )) for arithmetic, possibly no $( ... )-style command substitution, small and undocumented upper limits on how big input can get, ...

You can probably get away with not bothering with that much compatibility, but if it's even an issue in the first place, I strongly recommend you consider a language that is less terrible than shell. The basic Perl interpreter is more likely to be available than Bash.

Share:
17,955

Related videos on Youtube

Michael Durrant
Author by

Michael Durrant

rails ruby rspec rock

Updated on September 18, 2022

Comments

  • Michael Durrant
    Michael Durrant over 1 year

    I know that I can use either as the first line of scripts to invoke the desired shell.

    Would #!/bin/sh be recommended if compatibility with all unix systems is an absolute requirement?

    In my case the only OS' I care about are Ubuntu (Debian) and OSX. Given that, could I use #!/bin/bash and be assured it would work on both systems?
    Would this also make it easier to use scripts with more modern and clearer syntax for commands? Does using #!/bin/sh also relate to using POSIX ?

    • Jakob Bennemann
      Jakob Bennemann over 9 years
      Probably worth noting that many distros have started merging /bin and /usr/bin. Resultingly, it's probably better to use #!/usr/bin/env <shname> for portability these days.
  • Michael Durrant
    Michael Durrant over 9 years
    Thanks. As originally stated "In my case the only OS' I care about are Ubuntu (Debian) and OSX.". These are the only 2 systems I've used (and I use them a lot) in the last 5 years, so that is 'why' I am writing a shell script that would only work on them. I have no need at all for a universal script and the limitations it would present.
  • zwol
    zwol over 9 years
    @MichaelDurrant In that case, you don't need to, and should not, write a shell script. You should instead use any of the multitude of better scripting languages that are an option when you don't need total portability.
  • Rick-777
    Rick-777 about 9 years
    Bash is a very useful tool but be careful not to use it for starting services that might be vulnerable to a Bash scripting bug, e.g. access.redhat.com/security/cve/CVE-2014-6271. Stick to sh for such tasks.
  • Score_Under
    Score_Under almost 9 years
    Rick-777 that vulnerability was vastly overblown and in my opinion your comment is FUD. If a system service runs under bash it is in no way vulnerable to that bug. It is only vulnerable to that bug if it forks off a bash process while allowing remote users direct access to one or more environment variables, like in FastCGI, and even then only on unpatched versions of bash. On systems with sh linked to bash, the vulnerability will not be mitigated by using sh.