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:
The eponymous first two characters of the shebang are
#! (hash–bang 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/bash or another interpreter, for example
#!/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.
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:
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.
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:
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
"-x -e" is a single argument, making
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.
bash you can also set these options in the script with the
set -x set -e
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.
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
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
bashso you need to be proficient in
bashanyway (and know the quirks of
zshis not included on the macOS Recovery System or other macOS installation environments, so you cannot write post-installation scripts for these contexts in
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
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 -s /bin/zsh Changing shell for armin. Password for armin:
The shell that interprets a script is, of course, set with the shebang.
- the first line in a shell script starts with a shebang
#!and tells the system which tool interprets the script
envshould 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/bashas the shebang
- the ‘step up’ from
bashscripting 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
5 thoughts on “On the Shebang”
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”
Thank you! Fixed the line.
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.
Thank you, Chris. Glad you like it. I have fixed the layout
On macOS, the shebang interpreter must be an executable binary. On Linux, it can be a shell script.
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.
Comments are closed.