SkillByAIOpen interactive version →

Lesson 23 / 25

When Not to Use Bash, and Portability

Recognise when to switch languages and how to write portable shell.

Know the edges of the tool

Bash is excellent for gluing commands together: orchestration, file shuffling, deployment steps, CI tasks and quick automation. It becomes the wrong tool when a script needs complex data structures (nested maps, objects), floating-point maths, structured data beyond what jq handles comfortably, robust error handling across many steps, unit tests for intricate logic, concurrency beyond simple background jobs, or grows beyond a few hundred lines. At that point, Python, Go or another language usually gives clearer, safer code. A common rule of thumb from Google's shell style guide: if a script is more than about 100 lines or uses non-trivial control flow, consider rewriting it in a more structured language. For portability, decide your target: if a script must run under /bin/sh on minimal systems (Alpine's BusyBox, Debian's dash), write POSIX sh without arrays, [[ ]], local (not POSIX, though widely supported) or $(( ))-only features, and check with shellcheck --shell=sh. If you need Bash, require it explicitly and know which version: macOS's built-in 3.2 lacks associative arrays, mapfile and ${var,,}.

Bash-only features and POSIX alternatives

Useful when a script must run under /bin/sh.

bash feature                 POSIX sh alternative
---------------------------  ----------------------------------------------
[[ $a == b* ]]               case $a in b*) ... ;; esac
[[ -n $x && -f $f ]]         [ -n "$x" ] && [ -f "$f" ]
arrays: list=(a b c)         "$@" via set -- a b c, or newline-separated text
$(( i++ ))                   i=$((i + 1))
function f { local v; }      f() { v=...; }   (local is common but not POSIX)
<(cmd) process substitution  temporary files or named pipes (mkfifo)
${var^^}                     printf '%s' "$var" | tr '[:lower:]' '[:upper:]'
read -a, mapfile             while read loops

Duct tape and a toolbox

Bash is the best duct tape in the world: perfect for quickly joining things that were never designed to fit. But you would not build a house out of duct tape; at some point you reach for a proper toolbox.

Quick check: Which situation most strongly suggests rewriting a Bash script in Python or Go?

  • The script runs three commands in order
  • The script has grown to hundreds of lines with nested data structures and floating-point maths
  • The script needs to set an environment variable
  • The script uses a here-document
Answer

The script has grown to hundreds of lines with nested data structures and floating-point maths — Complex data and logic are where Bash becomes error-prone and hard to maintain.