# When Not to Use Bash, and Portability — Bash / Shell Scripting

Source: https://www.skillbyai.com/en/bash/p-limits

> 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.

```text
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.

**Quiz:** Which situation most strongly suggests rewriting a Bash script in Python or Go?

- [ ] The script runs three commands in order
- [x] 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.
