skip to content

macOS has shipped zsh as the default login shell since Catalina, yet /bin/bash on a current Mac is still version 3.2. Explain both facts and what they mean for shell scripts you ship to Macs.

level: middleimportance: should knowfreq 54%

answer

  1. a licence change froze one of them
  2. the other was swapped in around 2019
  3. associative arrays are the usual casualty
  4. the shebang decides, not your prompt
  5. Homebrew adds a bash, it cannot replace one

basics

~20 s

Apple made zsh the default login shell in macOS Catalina and kept bash only as a legacy interpreter, frozen at 3.2 because that is the last release under GPLv2 and Apple does not ship GPLv3 software. Scripts with a /bin/bash shebang therefore run on a 2007 bash.

solid answer

~50 s

Two separate facts. The **login shell** default changed to zsh in macOS Catalina (10.15); accounts created before that keep bash, and interactive bash sessions print a deprecation notice you can silence with `BASH_SILENCE_DEPRECATION_WARNING=1`. Separately, `/bin/bash` is still bash 3.2 from 2007 — the last release licensed under GPLv2 — because Apple stopped shipping GPLv3 software. The consequence for scripts is the important half: a `#!/bin/bash` shebang on macOS gets a bash with no associative arrays (`declare -A`), no `mapfile`/`readarray`, no `${var,,}` case conversion, no `globstar` and no `wait -n`, all of which your Linux CI has. The usual fixes are to write POSIX-compatible scripts, or to install a modern bash with `brew install bash` and use `#!/usr/bin/env bash` so PATH selects it. Note that the user's login shell is irrelevant to a script — the shebang decides which interpreter runs.

code

bash · 4 lines
bash
# What the OS gives you versus what Homebrew gives you.
/bin/bash --version | head -1
/bin/zsh --version
"$(brew --prefix)/bin/bash" --version | head -1

go deeper

for a junior

Know that zsh is the default shell on modern Macs and that /bin/bash is a very old bash 3.2, so a script written against newer bash may fail on macOS even though bash exists.

for a middle

Explain both causes — the Catalina default-shell change and the GPLv3 licensing freeze — and name concrete bash 4 features that are missing, such as associative arrays and mapfile.

for a senior

Make the portability call: write POSIX-compatible scripts or declare and check a bash version requirement up front, and understand that env-based shebangs shift the outcome onto the caller's PATH rather than guaranteeing anything.

for a principal

Set the policy: decide whether the organisation's operational scripts must run on a stock Mac at all, since 'requires Homebrew bash' is a real onboarding dependency, and treat licensing-driven platform divergence as a constraint to design around.

## Fact one: the default login shell is zsh Starting with macOS Catalina (10.15, 2019), new user accounts get `/bin/zsh` as their login shell. Accounts that existed before an upgrade keep whatever they had, which is why on a long-lived machine you still meet people whose Terminal opens bash. macOS nudges them: an interactive bash session prints a notice saying zsh is now the default, suppressible with `BASH_SILENCE_DEPRECATION_WARNING=1`. Changing your own login shell is `chsh -s /bin/zsh`, and the target must be listed in `/etc/shells` — which is exactly why a Homebrew-installed bash or fish has to be added to that file before `chsh` will accept it. For a Linux engineer arriving on a Mac, the practical differences at the prompt are mild — zsh is close enough to bash for interactive use that most muscle memory survives — but zsh is a *different language* for scripting, with its own array indexing and its own word-splitting rules. That is why nobody writes portable scripts against zsh, and it does not affect scripts at all as long as they carry a shebang. ## Fact two: /bin/bash is frozen at 3.2 bash 4.0 was relicensed under GPLv3, and Apple does not ship GPLv3 software. So macOS's bundled bash stopped at 3.2, released in 2007, and it is still 3.2 on current macOS. The same licensing decision explains the wider shape of the macOS command line: a BSD userland instead of GNU coreutils, an ancient bundled bash, and no bundled GNU toolchain. Apple's own path forward was zsh, which is permissively licensed and could be updated freely. bash remains for compatibility with the enormous number of existing `#!/bin/bash` scripts. ## What bash 3.2 does not have This is the part that produces real bugs, because the script *starts* and then fails somewhere in the middle: - **Associative arrays** — `declare -A map` is bash 4. On 3.2 you get an error, and the workaround is usually a different data structure entirely. - **`mapfile` / `readarray`** — bash 4. Read a file into an array with a `while read` loop instead. - **Case-conversion parameter expansion** — `${var^^}` and `${var,,}` are bash 4. Use `tr` on 3.2. - **`globstar`** — recursive `**` matching is bash 4. Use `find` instead. - **`wait -n`**, waiting for the first of several background jobs, is bash 4.3. A script that uses any of these works perfectly on the author's Linux box and dies on a Mac, often only on the branch that uses the feature. ## Choosing the shebang ```bash #!/bin/bash # macOS: guaranteed bash 3.2 #!/usr/bin/env bash # first bash on PATH — the Homebrew bash 5 if installed #!/bin/sh # POSIX shell semantics, no bash extensions at all ``` `#!/usr/bin/env bash` is the common recommendation because it lets a modern bash win, but be honest about the tradeoff: it makes the script's behaviour depend on the caller's PATH. On a Mac with `brew install bash` and the prefix ahead of `/usr/bin` you get bash 5; on a clean Mac you silently get 3.2 again. It buys you a *chance* at bash 4+ features, not a guarantee. If a script genuinely requires bash 4 or newer, say so and check: ```bash if (( BASH_VERSINFO[0] < 4 )); then echo "this script needs bash 4 or newer; try: brew install bash" >&2 exit 1 fi ``` Failing loudly at line 3 beats failing obscurely at line 300. ## The confusion worth pre-empting People conflate the login shell with the script interpreter. They do not interact. If your login shell is zsh and you execute a file whose shebang is `#!/bin/bash`, the kernel runs bash — your interactive shell has no say. Conversely, changing someone's login shell fixes nothing about a script that fails on bash 3.2. The only case where the login shell matters is when you *source* a file into the current shell rather than executing it. ## Installing a modern bash ```bash brew install bash # bash 5 under the Homebrew prefix $(brew --prefix)/bin/bash --version ``` Homebrew installs it into its own prefix; it does not and cannot replace `/bin/bash`, which lives on the read-only system volume. To make it a login shell, add its path to `/etc/shells` and then `chsh -s`. Anything with a hard `#!/bin/bash` shebang will still use Apple's 3.2 — installing bash 5 changes what *your PATH* resolves, not what the OS uses.

  • Does changing your login shell to zsh affect a script whose shebang is #!/bin/bash?
    No. The shebang selects the interpreter when the file is executed, so bash 3.2 runs it regardless of what your terminal uses. The login shell only matters when you source a file into the current shell instead of executing it. Conflating the two leads people to 'fix' a failing script by changing their prompt.
  • Why does `brew install bash` not fix a script that hardcodes #!/bin/bash?
    Homebrew installs bash 5 inside its own prefix and cannot touch /bin/bash, which lives on the read-only system volume. A hardcoded /bin/bash shebang still runs Apple's 3.2. Only `#!/usr/bin/env bash`, with the Homebrew prefix ahead of /usr/bin on PATH, picks up the newer interpreter.
  • Name a bash 4 feature that breaks on macOS and what you would use instead.
    `declare -A` associative arrays are the usual one; on bash 3.2 you restructure around parallel indexed arrays or an external tool. `mapfile` becomes a `while IFS= read -r` loop, `${var,,}` becomes `tr`, and globstar's `**` becomes `find`. Each substitution is POSIX-friendly, which is usually the better script anyway.

saying these in an interview costs you the question

  • Thinking Apple simply never bothered to update bash
  • Believing the login shell determines a script's interpreter
  • Assuming brew install bash replaces /bin/bash
  • Expecting bash 4 parameter expansions to work on macOS
  • Treating #!/usr/bin/env bash as a guarantee of a modern bash

context