Portability and POSIX sh
Prerequisites: Conditionals: if and tests, Arrays
POSIX sh is the standard shell language that every Unix-like
system provides. Bash implements it and adds many extensions
("bashisms"). Scripts written in plain POSIX sh run anywhere: in
minimal Docker images (Alpine uses BusyBox ash), on Debian and Ubuntu
(where /bin/sh is dash), on BSDs, and in embedded systems.
When portability matters
- Scripts in Docker images that may not include Bash.
- Installation scripts (
curl ... | sh) that must run on any system. - Git hooks and system scripts shared across different machines.
For your own tools on machines where you control the software, Bash's
extra features are usually worth it — just use #!/usr/bin/env bash,
not #!/bin/sh, so the right shell runs.
Common bashisms and their POSIX equivalents
| Bash | POSIX sh |
|---|---|
[[ $a == "$b" ]] |
[ "$a" = "$b" ] |
[[ $s == prefix* ]] |
case $s in prefix*) ... ;; esac |
(( n > 5 )) |
[ "$n" -gt 5 ] |
(( n++ )) |
n=$((n + 1)) |
arrays arr=(a b) |
set -- a b and "$@" (one list only) |
function name { } |
name() { } |
echo -e "a\tb" |
printf 'a\tb\n' |
source file |
. file |
$'\n' |
a literal newline in quotes |
local var |
not in POSIX (widely supported anyway) |
<<< "string" |
printf '%s\n' "string" | |
A portable script
cat > count.sh <<'EOF'
#!/bin/sh
set -eu
if [ "$#" -lt 1 ]; then
printf 'Usage: %s <dir>\n' "$0" >&2
exit 2
fi
count=0
for f in "$1"/*; do
[ -f "$f" ] || continue
count=$((count + 1))
done
printf '%s contains %d file(s)\n' "$1" "$count"
EOF
mkdir -p box && touch box/a box/b
sh count.sh box
box contains 2 file(s)
Checking portability
- Run the script with
dashorbusybox sh, which reject most bashisms. - ShellCheck checks against the shebang: with
#!/bin/sh, it warns about every Bash-only feature. checkbashisms(from Debian's devscripts) is another checker.
Other portability traps
Tools differ too, not just shells: GNU (Linux) versus BSD (macOS) versions
of sed -i, date -d, grep -P, readlink -f, and stat take different
options. Test on the systems you support, or install GNU tools on macOS
with Homebrew's coreutils, gnu-sed, and grep packages.