Bash vs sh — what is the actual difference?
Bash vs sh: Bash adds arrays, [[ ]] and (( )); sh is POSIX-only. What works in each shell, what breaks, and how to choose the right shebang.
Tldr
sh is the POSIX shell specification — a minimal language implemented by dash, ash, ksh, and others. Bash is one specific shell that implements all of sh plus dozens of extensions (arrays, [[ ]] tests, (( )) arithmetic, process substitution, case-conversion expansions). Write #!/usr/bin/env bash when you want Bash features; write #!/bin/sh and stick to POSIX syntax when portability across minimal containers matters.
Sections
Heading
What "sh" actually is on your system
Body
On macOS, /bin/sh is Bash running in POSIX mode. On Debian and Ubuntu, /bin/sh is dash. On Alpine Linux and most minimal containers, /bin/sh is busybox ash. They all implement the POSIX shell spec but differ in performance, error messages, and which non-POSIX extensions leak through. "POSIX-compatible" is not a guarantee any specific extension works.
Heading
Features that only exist in Bash
Body
Arrays (arr=(a b c), "${arr[@]}"), the [[ keyword ]] test (with =~ regex matching), (( )) arithmetic evaluation, process substitution <(cmd) and >(cmd), brace expansion {1..10}, the local keyword inside functions, case-conversion expansions (${var^^}, ${var,,}), pattern substitution (${var//a/b}), and indirect expansion (${!var}). All of these fail with "bad substitution" or "syntax error" under dash.
Heading
POSIX-safe alternatives
Body
For arrays, use space-separated strings and IFS or write to a temp file. For [[ ]], use [ ] and string concatenation. For (( )), use $(( )) arithmetic expansion. For local, use a subshell ( ... ). For case conversion, pipe to tr or awk. The portability tradeoff is verbosity — POSIX code is harder to read but runs on every Unix shell.
Heading
How to decide
Body
If your script ships in a Docker image based on alpine, debian-slim, or distroless, write POSIX sh — bash may not be installed. If your script targets developer laptops, CI runners, or full Linux distros, write Bash — it is everywhere and the syntax is nicer. If you are not sure, write Bash and document the dependency.
Verdict
Default to #!/usr/bin/env bash unless you have a specific reason to need POSIX sh — usually a minimal container without bash installed.
Faq
Q
Does #!/bin/sh guarantee POSIX behavior?
A
No. It guarantees the system's "sh" runs the script — and that shell may have non-POSIX extensions. Run shellcheck with --shell=sh to enforce POSIX rules at lint time.
Q
Is Bash slower than dash?
A
Yes, by ~2-5x for shell-loop-heavy workloads. dash starts faster and has less overhead per command, which is why Debian uses it for system scripts.
Q
Can I detect at runtime which shell I am in?
A
Check $BASH_VERSION — set in Bash, empty in dash/sh. For more detail, ps -p $ shows the actual interpreter.