Skip to content
AgileLogics
6 min read

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 dash or busybox 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.