The original Bourne shell (sh) was written by Stephen Bourne at Bell Labs and shipped with Version 7 Unix in 1979. Almost nobody runs that shell today. What survives is its behaviour, standardised by POSIX as the Shell Command Language.
/bin/sh is a standard, not a program¶
sh is the POSIX shell specification, and /bin/sh is whatever shell the distribution chose to provide POSIX sh behaviour:
| System | /bin/sh is |
|---|---|
| Debian, Ubuntu | dash |
| Fedora, RHEL, Arch, openSUSE | bash, running in POSIX mode |
| Alpine, BusyBox-based images | BusyBox ash |
| macOS | bash 3.2 in POSIX mode by default |
| FreeBSD | the Almquist-derived FreeBSD sh |
A script that starts with #!/bin/sh but uses bash features ([[, arrays, source, function, {1..5}) works on Fedora, where sh is bash, and fails on Debian or Alpine. Either write portable POSIX sh or use #!/usr/bin/env bash and admit the dependency. checkbashisms (Debian's devscripts package) and shellcheck --shell=sh catch bash-only syntax in sh scripts.
Portable equivalents of common bashisms¶
| bash | POSIX sh |
|---|---|
[[ $a == "$b" ]] |
[ "$a" = "$b" ] (always quote, single =) |
[[ $f == *.txt ]] |
case $f in *.txt) ... ;; esac |
source file |
. ./file |
function f { ...; } |
f() { ...; } |
echo -e "a\tb" |
printf 'a\tb\n' |
{1..5} |
seq 1 5, or a while loop with a counter |
arr=(a b c); "${arr[@]}" |
set -- a b c; "$@" (the positional parameters are the only array) |
${var^^} |
printf '%s' "$var" \| tr '[:lower:]' '[:upper:]' |
${var//a/b} |
printf '%s' "$var" \| sed 's/a/b/g' |
${var:0:3} |
printf '%s' "$var" \| cut -c1-3 |
<(cmd) |
a temporary file from mktemp, or a pipe |
$RANDOM |
awk 'BEGIN { srand(); print int(rand() * 32768) }' |
read -p "Name: " n |
printf 'Name: '; read -r n |
&>file |
>file 2>&1 |
Still portable and worth using: ${var#prefix}, ${var%suffix}, ${var:-default}, ${#var}, $(( )) arithmetic, $(cmd), case, getopts, and functions.
Checking scripts¶
shellcheck --shell=sh script.sh # warns on bash-only syntax
checkbashisms script.sh # Debian devscripts
dash -n script.sh # parse with a strict POSIX shell without running it
Testing a script under dash is the fastest practical check for portability, because dash implements very little beyond POSIX. See dash and BusyBox ash for the two sh implementations you're most likely to hit, and bash for what bash adds.
Further reading¶
- POSIX Shell Command Language: the grammar and special builtins, the definitive reference for what is portable
- Rich's sh (POSIX shell) tricks: portable idioms from the author of musl libc
- ShellCheck