Bash (Bourne-Again SHell) is a Unix shell written by Brian Fox in 1989 for the Free Software Foundation as a free replacement for sh. It took the Bourne shell — a program that was already complete — and added features. Then added more features. Then became the default shell on Linux, macOS, and virtually every Unix-like system, not because it was the best shell but because it was free, it was compatible, and it was there.
Bash is the default. “Default” is not a compliment. “Default” is what happens when nobody makes a choice.
“Bash is the shell you use until you discover you have opinions.”
— The Caffeinated Squirrel, who discovered zsh and never looked back
The Features
Bash added to sh: arrays, associative arrays, arithmetic expansion, brace expansion, command-line history, tab completion, programmable completion, here strings, process substitution, and a regex operator. Each feature was useful. Each feature made Bash more than sh. Each feature made Bash less portable than sh.
This is the eternal trade-off: every feature you add to a standard is a feature that breaks on systems that implement only the standard. A Bash script with arrays is more powerful than a sh script without them. A Bash script with arrays also doesn’t run on Alpine Linux’s default shell (ash), on FreeBSD’s default shell (sh), or on any system where /bin/sh is not Bash.
The developer who writes #!/bin/bash has made a different choice than the developer who writes #!/bin/sh. The Bash developer has chosen power over portability. The sh developer has chosen forever.
The 400-Line Script
There is a moment in every Bash script’s life — around line 200, sometimes earlier, rarely later — when the developer should switch to Python.
The moment arrives when the script needs a data structure more complex than a string, an error handling model more sophisticated than set -e, a parsing task more nuanced than awk '{print $3}', or a test suite. The moment arrives, and the developer does not switch to Python, because the script is already 200 lines long and “it’s almost done” and “it’s just one more function” and the function is 50 lines and uses eval and the developer has created something that works, that nobody else can read, and that will haunt the CI/CD pipeline for the next five years.
Every team has a 400-line Bash script. Nobody knows who wrote it. Nobody wants to touch it. It runs in production. It has no tests. It uses eval. It works. It is feared.
“The 400-line Bash script is the software equivalent of a load-bearing wall. Nobody put it there on purpose. Nobody can remove it safely. It supports everything above it. It has no documentation. It has
eval.”
— A Passing AI, surveying CI/CD pipelines at 3 AM
The .bashrc
The relationship between .bashrc, .bash_profile, .profile, and .bash_login is a mystery that has been explained a thousand times and understood by no one.
.bash_profile is loaded for login shells. .bashrc is loaded for interactive non-login shells. .profile is loaded for login shells if .bash_profile doesn’t exist. .bash_login is loaded if .bash_profile doesn’t exist but before .profile. On macOS, Terminal.app starts a login shell, so .bash_profile loads. On Linux, the terminal emulator starts a non-login shell, so .bashrc loads. Unless it doesn’t. Unless something sources something else.
The standard solution is to put everything in .bashrc and source .bashrc from .bash_profile. The standard problem is that every developer discovers this solution independently, after two hours of debugging why their PATH is wrong.
Measured Characteristics
Year created: 1989
Creator: Brian Fox (Free Software Foundation)
Based on: sh (Bourne shell, 1979)
Default shell on: Linux (most distros), macOS (until 2019)
Replaced as macOS default by: zsh (2019 — licensing, not quality)
Features added over sh: arrays, arithmetic, history, completion, regex
Portability vs sh: lower (bashisms break on non-Bash shells)
Lines at which a Bash script should become Python: ~200
Lines at which it actually becomes Python: never (the script is in production now)
The 400-line script: every team has one
Configuration files: .bashrc, .bash_profile, .profile, .bash_login (the mystery)
Stack Overflow questions about Bash: all of them
The eval keyword: present (and feared)
See Also
- sh — The shell Bash replaced. sh was sufficient. Bash was free and sufficient. Free won.
- zsh — The shell that replaced Bash on developer laptops. Everything Bash does, plus more, plus oh-my-zsh.
- fish — The shell that looked at Bash’s syntax and said “no.”
- PowerShell — Microsoft’s shell. Objects instead of text. A different philosophy. A different planet.
- cmd — The shell Bash replaced on Windows (via WSL). cmd did not go quietly. cmd is still there.
