bash Is Not sh
As promised, this is a post about shebang usage, shell script portability, and bash vs sh syntax.
TL;DR
If you need bashisms, use #!/usr/bin/env bash
.
If you want to use #!/bin/sh
, make sure your code can run on an sh
implementation other than bash
!
What is sh
?
sh
, or the Bourne Shell, was a shell first introduced in 1977 with
Research UNIX 7, from Bell labs.
sh
was written by Stephen Bourne, as a replacement for earlier UNIX shells.
sh
takes much of its syntax from ALGOL68, a programming language first
introduced in 1968. While most folks have never written a line of ALGOL68,
they’d likely be very familiar with the syntax! Structures like if
fi
, and
case
esac
, come from ALGOL.
For many years, sh
was the default UNIX shell, and it inspired successors
like GNU’s bash
, and the Almquist shell ash
currently in use by FreeBSD,
NetBSD, Debian and Ubuntu (as dash
), and BusyBox!
What is bash
?
bash
, or the Bourne-Again Shell, is a GNU replacement for the Bourne Shell,
which seeks to extend sh
’s syntax.
While bash
is perfectly capable of running sh
scripts, it brings some extra
non-portable features to the table.
bash
vs sh
sh
is the original implementation of the language.
bash
seeks to replace and extend the language, as a GNU developed clone.
This is not inherently bad.
However, as you will see, assuming that everyone has bash
can be a bad thing.
Location location location!
By default, there is always an sh
at /bin/sh
.
This leads to the common usage of #!/bin/sh
as the shebang in most shell
scripts.
When writing POSIX sh
, or adhering to the common feature set shared by sh
,
ash
, dash
, and bash
, this is fine!
However, on Linux, /bin/sh
tends to be a symlink to bash
, which on Linux
resides usually at /bin/bash
.
When using the #!/bin/sh
shebang and writing bashisms, you inadvertently
open yourself up to portability issues, as bashisms can only be interpreted by
bash
.
For more on bashisms, see:
https://en.wikipedia.org/wiki/Bash_(Unix_shell)#Portability
The Debian project famously ran into this issue when replacing their /bin/sh
with dash
as opposed to bash
.
The same occurred with Ubuntu.
https://wiki.ubuntu.com/DashAsBinSh
To quote:
“there have been a certain number of shell scripts written specifically for
Linux systems, some of which incorrectly stated that they could run with
/bin/sh
when in fact they required bash
, and these scripts will have
broken due to this change.”
As you can see, assuming /bin/sh
is bash
can lead to some problems!
Why not just use /bin/bash
then?
Ah! #!/bin/bash
, the favorite of many Linux shell programmers who believe
they’ve solved the issue of assuming /bin/sh
is bash
!
Why not just specify that you want bash
?
While the logic is sound, there’s still a problem.
See, many OSes that aren’t Linux (yes, Linux isn’t UNIX and wasn’t even the
first UNIX-like,) either don’t have bash
installed at all, or have it
somewhere other than /bin/bash
!
For example, in FreeBSD, we use the Almquist Shell, ash
, as our /bin/sh
.
ash
is a very light re-implementation of sh
. We don’t have bash
as part
of the base system at all!
If one wants to use bash
, they can. A simple pkg install bash
will do the
trick! However, ports and Base are separate in FreeBSD.
This means bash
will install to /usr/local/bin/bash
, and not /bin/bash
.
I’m sure you can see the problem of using #!/bin/bash
now: it won’t work
on anything other than Linux distros that have bash
installed to /bin/
!
What then, can we do?
env is fucking magic!
As any python
programmer will undoubtedly be familiar with, the shebang
#!/usr/bin/env python
is the recommended way to call python
.
This too holds true for any shell script that doesn’t adhere to POSIX or the
common set of features amongst sh
and its replacements and clones!
If you wish to use #!/bin/sh
, you should be sticking to these minimal common
features, as a matter of course. It may be a little harder, but it supports
portability between platforms and ensures your code will run on the widest
variety of systems there is!
But I NEEEEEED bash
features! (I don’t want to use awk
for arrays)
That’s fine! As mentioned before, env
is fucking magic!
It will search your $PATH
, for the interpreter specified, and use it!
While we’ve established that #!/bin/bash
isn’t portable, #!/usr/bin/env bash
is!
Using #!/usr/bin/env bash
will result in env
searching your $PATH
for
any version of bash
it can find.
This means whether bash
is at /bin/bash
, /usr/local/bin/bash
, or anywhere
else, you’ll be able to correctly call bash
and use them there bashisms to
your heart’s content!
Additionally, it serves as a notice to anyone seeking to use your program
that it requires bash
, and not ash
, sh
, or any other sh
re-implementation.
This is especially important for code you want to ensure is run correctly on non-Linux systems.
But I can’t find any resources on pure sh
! (How do I know if it’s a bashism?)
Do you want to write pure sh
? In my opinion, this is the best way to go,
though I understand many folks like their bashisms, or simply don’t know
any better.
Resources are available all over the internet!
The FreeBSD ash
documentation is a great start.
Additionally, using a linter like shellcheck
is a great way to find those
sneaky bashisms and learn new ways of doing The Thing!
Can you really write good shell code without bashisms?
Yup!
Perfect examples are iocage Legacy (or the fork iocell
), a FreeBSD jail
manager written in pure sh
, and vzvol
, a ZFS zvol manager written by
yours truly.
You can find iocell
at https://github.com/bartekrutkowski/iocell
and vzvol
at https://github.com/RainbowHackerHorse/vzvol
vzvol
is also a great example of modular shell code, which is a great way
to ensure extensibility and cleanliness of code.
I source functions from files the same way you would import functions
and libraries in C.
Closing Thoughts
bash
isn’t bad. This isn’t me bashing bash
:)
I just want things Done Right, so we can all benefit from each others’ code
without having to modify or edit!
Resources
Examples of pure sh
code
https://github.com/RainbowHackerHorse/vzvol https://github.com/bartekrutkowski/iocell
Linters
https://github.com/koalaman/shellcheck
bashisms
https://mywiki.wooledge.org/Bashism
Writing pure sh
https://en.wikipedia.org/wiki/The_Unix_Programming_Environment https://legacy.gitbook.com/book/freebsdfrau/serious-shell-programming/details
Using env for Portability (Thanks, @nixcraft !)
https://www.cyberciti.biz/tips/finding-bash-perl-python-portably-using-env.html