On the Shebang

Every script you want to run from the command line should have a shebang as the first line.

Note: I talked about this in my MacSysAdmin talk. I wanted to go into more detail here.

You can have scripts without the shebang. In this case the shell that you launch the script from will be used to interpret the script. This can, obviously, lead to complications, on other systems or with other users.

A shebang looks like this:

#!/bin/bash

The eponymous first two characters of the shebang are #! (hashbang or shebang). These two characters form a ‘magic number.’ Files are usually identified by certain codes (i.e. magic numbers) in the first few bytes of data. The hex code 23 21 converts to the ascii characters #! and tells the system that a file is script.

After the shebang comes the command that should interpret the script (the interpreter). This is either a shell, as in #!/bin/sh or #!/bin/bash or another interpreter, for example #!/usr/bin/python or #!/usr/bin/osascript. Generally, any command that can interpret text files or streams can be used in the shebang.

Since the status or value of the PATH variable is not guaranteed or certain in many contexts that scripts can be run in, the path after the shebang needs to be absolute.

The env shebang

There are environments where you cannot predict the absolute path of a given tool. For example the bash v3.2 shell on macOS is installed by default in /bin/bash. Users can also download and install bash version 4.4 onto their computers. The location for the the bash 4 binary is usually at /usr/local/bin/bash (this might be different depending on the installation method you used). Since /usr/local/bin is the first item of the default PATH on macOS the newer bash 4 will be chosen before the built-in bash 3.2 when the user types bash into their shell.

When you use the absoute path to /bin/bash in a shebang, you are ensuring that the macOS provided built-in version of bash will be used. For macOS system administrators, this should be the preferred shebang, since it provides a known, certain environment.

However, there are cases where you want scripts to be run with the user’s preferred tool, rather than a set path. You may also want your script to be able to on multiple different unix (and unix-like) systems where the location of the bash binary can be unpredictable. In this case you can use the /usr/bin/env tool as the shebang with bash (or another interpreter) as the parameter:

#!/usr/bin/env bash

This shebang means: “determine the preferred bash tool in the user’s environment and use that to interpret the script.”

Note: obviously this also presumes the absolute path to the /usr/bin/env. However, most unix and unix-like system seem to agree here.

Shebang Arguments

The shebang line can take a single argument. For weird syntactic reasons the entire line after the interpreter path is passed as a single argument to the interpreter tool.

So a shebang line that looks like this:

#!/bin/bash -x

will be executed like this:

/bin/bash "-x" /path/to/script

But if you added another variable to the shebang:

#!/bin/bash -x -e

then it would be executed as

/bin/bash/ "-x -e" /path/to/script

where "-x -e" is a single argument, making bash choke.

Some tools (like env) that are regularly used in shebangs are able to split the single argument. bash is not one of them. Read a tool’s man page and documentation and test. In general it is considered bad style to add arguments or options to the shebang.

Note: with bash you can also set these options in the script with the set command:

set -x
set -e

bash versus sh

Many Unix and unix-like systems have sh as well as bash and other shells available. sh goes back to the very early UNIX shells in the seventies. sh has survived because it serves as the lowest common standard for shell scripting. These standards are defined in the POSIX specification, though POSIX defines more than just shell scripting.

When you have to build scripts that need to run across many different flavors and versions of Unix and Linux, where you cannot rely on bash being present, then conforming to POSIX and sh might be necessary.

Note: bash is also POSIX compliant, but it has more features. When you script against sh you ensure you don’t use any of those additional features.

However, as macOS administrators, we can rely on a larger ‘common’ set. bash has been present on macOS since Mac OS X 10.0 (and earlier incarnations in NeXTSTEP). bash gives you several extra features over plain sh such as better testing with the double square bracket, ‘here documents’, parameter substitution and arrays.

As a macOS system administrator you should leverage these and always choose /bin/bash over /bin/sh.

bash versus Other Shells

The argument that bash is better than sh works for other shells as well. You can make a very good argument that other shells are better than bash, or at the very least bash version 3 included in macOS. I often hear this with zsh, so I will be using zsh as an example, but the arguments work with other shells as well.

(You can find a very good comparison of different shells here.)

zsh and other shells have many features that bash 3 (and even 4) lacks. There are two main reasons I would still recommend bash for scripting:

  • most script examples and shared code is written in bash so you need to be proficient in bash anyway (and know the quirks of bash)
  • the zsh is not included on the macOS Recovery System or other macOS installation environments, so you cannot write post-installation scripts for these contexts in zsh

When you do reach the limitations of bash for scripting, other languages such as Python and Swift are even more useful and powerful on macOS than zsh, so the step up from bash should not be another shell, but a different scripting language entirely

Interactive shell versus shell scripting

While I argue that that other shells don’t have that much edge on bash for scripting, they certainly do have an edge on bash in some of their interactive and customization features.

Two of the more interesting interactive shells for macOS are zsh and fish.

The good news here is that you can set one shell for interactive use, and still continue using and writing bash scripts and get the best of both worlds.

The default interactive shell is set in your user record, you can change this in the ‘Advanced Options’ in the Users & Groups preference pane, or with chsh command:

$ chsh -s /bin/zsh
Changing shell for armin.
Password for armin: 

The shell that interprets a script is, of course, set with the shebang.

Summary

  • the first line in a shell script starts with a shebang #! and tells the system which tool interprets the script
  • env should only be used in the shebang when the script needs to run in environments where you cannot predict the location of the actual interpreter. For admins this introduces an extra level of uncertainty
  • macOS administrators should use /bin/bash as the shebang
  • the ‘step up’ from bash scripting should be a more complex language with native macOS API access such as Python or Swift
  • you can use one shell for interactive Terminal work and another for scripting

Published by

ab

Mac Admin, Consultant, and Author

5 thoughts on “On the Shebang”

  1. Thanks for this post! On your second summary point you left the sentence incomplete: “env should only be used in the shebang when the script needs to”

  2. Excellent post, definitely one of the clearest, most straightforward and useful posts I’ve seen discussing the shebang!

    However, you may want to look at the second bullet point of `bash versus Other Shells` heading as two words get combined where I’m guessing you meant to have a third bullet point or possibly just another sentence.

  3. On macOS, the shebang interpreter must be an executable binary. On Linux, it can be a shell script.
    https://stackoverflow.com/questions/9988125/shebang-pointing-to-script-also-having-shebang-is-effectively-ignored

    I ran into this limitation because I am trying to write my own “env” script which will create the environment before invoking the interpreter. Why? Apparently, the system Script menu launches scripts without propagating the environment à la bash/launchd.
    https://apple.stackexchange.com/questions/412455/script-menu-how-to-reference-user-path-utilities-path-environment-is-missing

Comments are closed.