// 1989 — 2026 · Brian Fox · GNU · Bourne-again shell · #!/usr/bin/env bash

$bash

Released in June 1989 by Brian Fox at the FSF — the name is Stallman's pun, "Bourne again shell". Thirty-seven years on it still sits underneath every Linux box, every Dockerfile, every CI/CD pipeline, every curl | bash installer. No "better language" has displaced shell — because no language ships pre-installed everywhere the way shell does.

1989Bash 1.0 · 8 Jun
Brian Fox · GNU / FSF
37 yrStill Linux's default
maintained by Chet Ramey
100%CI / Dockerfile default
no real challenger
25 yrShellshock latency 1989→2014
CVE-2014-6271
~/work — bash — 80×24
$ set -euo pipefail $ for f in *.log; do gzip "$f" done $ # still works in 2026 $ _
set -euo pipefailcmd | xargstrap EXIT$(date +%F)<<EOF${var%.*}find -print0#!/bin/bash<(sort a)IFS=$'\n\t'[[ -f $f ]]curl | bash
scroll
01

What is bash

Bash is GNU's sh-compatible command-line shell, written by Brian Fox in 1989. The word "shell" means both an interactive command interpreter and a scripting language — its single most important design decision: what you type at the prompt and what you write in a file share the same syntax.

Superset of POSIX sh lineage

Bash is compatible with the 1977 Bourne shell + POSIX.2, and adds arrays, associative arrays, [[, $(), process substitution, brace expansion. A 30-year-old script still runs; new scripts don't have to pretend to live inside strict POSIX.

Interactive = scripted design

Prompt = script. A command you just validated at the prompt copies straight into a .sh — the distance from exploration to automation is zero. Python's REPL is still different from a .py; shell isn't.

Everything is a text stream philosophy

Unix philosophy made literal: programs read stdin, write stdout, do one thing. | wires them; > redirects; & backgrounds. The 2026 LLM tool-chain is a revival of the same idea.

Pre-installed everywhere ubiquity

The hardest-to-replicate advantage: every POSIX system has /bin/sh; nearly every Linux has /bin/bash. No runtime to install — anything you can ssh into, you can run your script on.

deploy.pyPython · the heavy way
import subprocess, pathlib, shutil
from contextlib import contextmanager

@contextmanager
def tmpdir():
    d = pathlib.Path("/tmp/build")
    d.mkdir(exist_ok=True)
    try: yield d
    finally: shutil.rmtree(d)

with tmpdir() as d:
    subprocess.run(["tar","xf",...], check=True)
    subprocess.run(["make"], cwd=d, check=True)

# 12 lines · subprocess everywhere · still calling shell tools
deploy.shbash · the native way
#!/usr/bin/env bash
set -euo pipefail

TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

tar -xf release.tgz -C "$TMP"
make -C "$TMP"

# 6 lines · cleanup guaranteed · no FFI
# runs on any POSIX box · no pip install
02

Family Tree : ShellLineage

Every modern shell traces back to the 1971 Thompson shell. Main line: Bourne (sh) → ksh → bash → zsh. Berkeley branch: csh → tcsh. Modern: fish (anti-POSIX) / nushell (Rust). Windows: PowerShell (object pipes). Bash sits on the thickest part of the main line.

1970s
1980s
1990s
2000s
2010s
2020s
Lineage
sh1977 · Bourneif / for / case
ksh1983 · Kornarrays / coprocs
bash1989 · FoxGNU flagship
Bourne main line
zsh1990 · Falstadksh ∪ bash ∪ tcsh
oh-my-zsh2009 · Russelldesktop hit
zsh = macOS2019 · CatalinaApple flips default
zsh branch
csh1979 · JoyC-flavoured
tcsh1981csh + readline
Berkeley branch
fish2005 · Liljencrantzanti-POSIX
nushell2019 · TurnerRust · structured
YSH / Oil2020+ · Chuupgrade path
Modern experiments
PowerShell 1.02006 · Snoverobject pipeline
pwsh OSS2016 · cross-platformLinux / macOS
Windows counter
Bourne / POSIX main line Berkeley csh branch 2000s+ redesigns PowerShell (object shell)
03

History : 55 yr timeline

From 1971's Thompson shell to 2026 — 55 years. The line runs through Bell Labs, Berkeley, GNU, Apple, Microsoft — and through one 25-year-old security hole (Shellshock) and one licensing fight (macOS leaving bash). Everyone complains about it. Everyone uses it.

  1. 1971

    Thompson shell — shipped with Unix V1

    Ken Thompson's first shell, written at Bell Labs for Unix V1, installed at /bin/sh (the name has stuck for 55 years). Minimal: no flow control, no variables, just process spawning and redirection. The origin of every modern shell.

  2. 1977

    Bourne shell (sh) — control flow lands

    Stephen Bourne at Bell Labs ships sh with if / for / while / case / functions / variables — all of it. The Algol-68-flavoured syntax is where the famous fi (reversed if) and esac (reversed case) come from. POSIX shell is its standardisation; /bin/sh on macOS today still means this.

  3. 1979

    C shell (csh) — the Berkeley fork

    Bill Joy at Berkeley writes csh with deliberately C-like syntax: if (cond) then ... endif. It introduces lovely interactive features — command history, job control, aliases — but its scripting semantics are infamously broken. Tom Christiansen's 1995 essay "Csh Programming Considered Harmful" is the canonical takedown.

  4. 1983

    Korn shell (ksh) — a refined Bourne superset

    David Korn at AT&T ships ksh: fully Bourne-compatible, but adds arrays, associative arrays, floats, regex, coprocesses. The long-time default shell on commercial Unixes (Solaris / AIX / HP-UX). ksh93 is still genuinely in production today for some finance / telecom backends.

  5. 1989·06·08

    Bash 1.0 — Brian Fox writes it for GNU

    On 8 June 1989 Brian Fox releases Bash 1.0 at the FSF — the name is Stallman's pun, "Bourne again shell". The goal: give GNU a Bourne-compatible shell free of AT&T's licence. Bash becomes the default on nearly every Linux distribution. Chet Ramey takes over maintenance in 1992 and is still doing it today.

  6. 1990

    zsh — Paul Falstad at Princeton

    The same year (1990) Paul Falstad writes zsh. Feature-wise it's the union of ksh + bash + tcsh: powerful completion, recursive globs (**/*.ts), rich parameter expansion, themes. It stays niche for years until oh-my-zsh (Robby Russell, 2009) puts it on every Mac developer's screen.

  7. 1992

    POSIX.2 / IEEE 1003.2 — shell standardised

    POSIX freezes a Bourne-shell subset into IEEE Std 1003.2. From here on, #!/bin/sh scripts have a formal cross-Unix portability contract. But "strict POSIX shell" is much harder to write than bash — no arrays, for one. The constraint keeps tools like checkbashisms in business to this day.

  8. 2005

    fish (friendly interactive shell)

    Axel Liljencrantz releases fish: deliberately POSIX-incompatible for a cleaner language — (...) instead of $(...), no surprise word-splitting, autosuggestions out of the box. Cost: copy-pasted bash one-liners just don't run. "We accept this is the price of breaking back-compat — but shell needed modernising".

  9. 2006·11

    PowerShell 1.0 — Microsoft's counter-experiment

    Jeffrey Snover's PowerShell ships 1.0. The pitch: pipe objects, not text — pipelines carry .NET objects end-to-end. The real-world answer to "what if shell were designed from scratch, with a type system". Open-sourced and cross-platform in 2016, still the default for Windows automation today.

  10. 2010+

    DevOps era — shell as CI/CD glue

    Containers + cloud bring shell roaring back into the spotlight: Dockerfile RUN lines, GitHub Actions run: blocks, K8s init containers, Ansible tasks, the Homebrew installer one-liner (/bin/bash -c "$(curl ...)"). No newer language has displaced shell because none of them is pre-installed everywhere.

  11. 2014·09·24

    Shellshock — a 25-year-old hole

    On 24 September Stéphane Chazelas discloses CVE-2014-6271: bash parses environment-variable function definitions and keeps executing the trailing code. The attack surface is huge — any service that pushes user input into env then invokes bash is exposed (CGI, DHCP, git-over-ssh). The bug had been in the code since 1989 — undetected for 25 years. Together with Heartbleed the same year, it cracked the "open source = secure" story.

  12. 2016

    PowerShell open-sourced, cross-platform

    Microsoft open-sources PowerShell and ships Linux / macOS builds. It moves from "the strange Windows-only option" to "a cross-platform contender". Today, choosing bash or pwsh for ops automation is a real decision rather than a function of which OS you booted into.

  13. 2019·10

    macOS Catalina — default shell flips from bash to zsh

    Apple didn't switch for technical reasons — it switched for licensing. Apple wouldn't ship bash 4+ (GPLv3), so it pinned macOS to bash 3.2 (2007, GPLv2) for over a decade. In October 2019 Catalina cuts the cord and ships zsh as the default (BSD-style licence). A symbolic moment: the world's richest company opts out of GPLv3 — and out of bash.

  14. 2019

    Nushell — a structured shell in Rust

    Jonathan Turner and Andrés Robalino kick off Nushell: structured data through pipelines (the PowerShell intuition) + a Rust implementation + modern syntax. ls | where size > 1mb reads like a table query. Niche but past 0.x maturity; it hasn't dented bash's deployment base, but it's taken the "shell should be better" early adopters.

  15. 2020+

    Oil shell / YSH — "a stricter bash"

    Andy Chu's Oil (later renamed YSH) takes a different angle: run bash scripts as-is, but offer a clean new language alongside. The philosophy: "bash can't be thrown away, but it needs an upward path." Small in size, but one of the few projects that thinks of shell as a designed language.

  16. 2026

    37 years in — bash is still here

    Bash in 2026: the default on nearly every Linux distribution, the default in every CI/CD system, the default in every Dockerfile, the default in every cloud-server bootstrap. Chet Ramey still maintains it (the 5.x series). Zsh has taken Mac desktop interactive use; PowerShell has taken Windows automation; but the server-side and CI scripting base is bash, period. A de-facto monopoly with no real challenger.

04

Language Essentials : BashGotchas

The eight cards below cover where bash differs hardest from other languages — and where scripts break: [ ] vs [[ ]], parameter expansion, strict mode, here-docs, process substitution, trap, xargs, $(). The ninth is a brief take on "is bash even a programming language".

A

[ ] vs [[ ]]

[ is an external command (POSIX test); [[ is a bash keyword. [[ ]] skips word-splitting, doesn't explode on unquoted vars, supports == glob matching. In bash, always use [[.

# fragile — splits if $f has spaces
if [ -f $f ]; then ...

# safe — quoting optional inside [[
if [[ -f $f && $f == *.log ]]; then ...
B

${var} parameter expansion

The lightweight replacement for sed/awk in shell: ${f%.*} strips suffix, ${f##*/} is basename, ${f:-default} is a default. Looks like line noise — but learning it saves hundreds of forks.

f=/var/log/app.log
echo $"${f##*/}"   # app.log
echo $"${f%.*}"    # /var/log/app
echo $"${name:-anon}" # default
C

set -euo pipefail

The strict-mode trio: -e exit on command failure, -u error on undefined-variable use, -o pipefail any pipe segment failing = pipeline failure. First line of every bash script. Pair with IFS=$'\\n\\t' to neutralise word-splitting ambushes.

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\\n\\t'

# now `cmd1 | cmd2` failing in cmd1 actually stops you
D

Here-docs <<EOF

Feed a multiline string as if it were a file. <<EOF expands variables; <<'EOF' (quoted) is fully literal. Git commit messages, inline SQL, generated config files — all of them lean on this.

cat >config.yaml <<EOF
host: $$HOSTNAME
port: 8080
EOF

# single-quoted EOF = no expansion
E

Process substitution <(...)

Hand a command's output as if it were a file path to another command — no temp file needed. diff <(sort a) <(sort b) is the canonical case: compare two sorted streams, never touch disk. bash/zsh only — strict POSIX doesn't have it.

diff <(sort a.txt) <(sort b.txt)

comm -12 <(a-cmd) <(b-cmd)
F

trap — exit-time cleanup

Register an exit hook. trap '...' EXIT = the handler runs once no matter how the script dies (normal return, kill, error-exit). The right place to clean up tempfiles, children, locks — not rm -rf $TMP before every exit.

TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

# do whatever; on any exit, $TMP is gone
G

xargs — pipe → command args

Turns stdin lines into the next command's arguments. find . -name '*.log' | xargs grep ERROR is the canonical move — but filenames with spaces blow it up; the safe version is find ... -print0 | xargs -0 or just find -exec. The connective tissue of shell orchestration.

find . -name '*.log' -print0 |
  xargs -0 grep -l ERROR

# -print0 / -0 = NUL-delimited, space-safe
H

Subshell $(...) vs backticks

Command substitution. New code uses $(cmd) — nestable, clean quoting. Backticks `cmd` are a 1977-sh artefact, deprecated even in POSIX (1992). You'll still see them — that's not a bug, that's 35 years of muscle memory.

# preferred
today=$(date +%F)

# legacy — nesting gets ugly fast
today=`date +%F`

Programming language? Formally yes, practically don't

Bash is Turing-complete — functions, loops, arrays, associative arrays, coprocesses. Formally a real language. But: no variable types, six layers of quoting rules, set -e corner cases worth a small book, implicit string/number coercion no one fully remembers. The existence of BashFAQ + ShellCheck is itself the answer.

"Anyone who's written more than 100 lines of bash without bugs is lying."

05

Idioms Hall of Fame : 9 patterns

These nine patterns will cut your Stack Overflow searches in half. Each has a decade-plus of stable form and is validated by ShellCheck. Memorise them and you're intermediate.

find . -name '*.ts' -print0 | xargs -0 grep -l TODO

Iterate a list of files

find the files, xargs them into grep — shell orchestration 101. For space-safety use -print0 / -0.

set -euo pipefail; IFS=$'\\n\\t'

Strict-mode opener

The first line of every production script. Turns silent failures into loud ones.

trap 'rm -rf "$TMP"' EXIT

Exit-time cleanup

trap EXIT — runs once however the script dies. The right home for tempfile / child cleanup.

diff <(sort a) <(sort b)

Diff two streams

Process substitution at its best: compare two command outputs without touching disk.

name=$"${path##*/}"; base=$"${name%.*}"

Path slicing

Parameter expansion strips suffixes / takes basenames — no sed/awk needed.

psql -d db <<SQL
SELECT 1;
SQL

Here-doc feeds SQL

Pipe multiline SQL straight into psql / mysql with no intermediate .sql file.

mkdir -p "$OUT/data/processed"

Idempotent mkdir

mkdir -p's "doesn't fail if it exists" is the 30-year-standard idempotency pattern.

until nc -z host 5432; do sleep 1; done

Wait for a port

A container-era staple: a loop with sleep and exit code waits for a service to be ready.

#!/usr/bin/env bash

Portable shebang

#!/usr/bin/env bash is more portable than the hardcoded #!/bin/bash (e.g. Homebrew bash lives in /opt/homebrew/...).

06

Why It's Still Here : WhyShellPersists

Shell has been complained about for 50 years and no replacement has stuck. Not because shell is great — because the slot it occupies is irreplaceable: pre-installed everywhere, pipes are Unix philosophy made literal, the syntax is 30-year backward compatible, and it's the glue of all of DevOps.

$

Pre-installed everywhere

This is the root of every other reason: bash ships by default on Linux, sh ships on every POSIX system. No runtime to install — write and run. When you ssh into a stranger's box, the only things you can count on are the shell and ls. Python? Maybe. Node? Definitely not.

# works on any *nix from 1989 onward
#!/bin/sh
echo "hello $(uname -s)"
|

Pipes as a universe model

Unix philosophy made literal: each program reads stdin, writes stdout, does one thing. cmd1 | cmd2 | cmd3 expresses composition in 22 bytes. The 2026 LLM tool-chain revived the same idea: tool calls, streamed tokens, and agent chains are all isomorphic to pipes.

curl -s api/data |
  jq '.[].id' |
  xargs -I{} curl -s api/detail/{}
#

The DevOps default

Dockerfile RUN, GitHub Actions run:, K8s init containers, Ansible shell:, the Homebrew one-line install, the official Rust / Node installers — all of them are bash underneath. If you write "infrastructure" you almost certainly need shell, even if your day-job language is Python / Go.

# the Homebrew installer, one line
/bin/bash -c "$(curl -fsSL .../install.sh)"
>

Glue stability — 30 years unbroken

A bash script written in 1989 probably still runs. Python 2 → 3 shattered tons of code, Node breaks APIs nearly every year, half of early Ruby's ecosystem is gone — shell doesn't have this problem. "Boring" is its greatest virtue.

# 1989 syntax · still works in 2026
for f in *.txt; do
  mv "$f" "$${f%.txt}.bak"
done
~

Interactive + scripted are the same language

Rare among programming languages, the interactive line and the script file share one syntax. A command you just ran in your history can be pasted into a .sh and it runs. The distance from "explore" to "automate" is zero. Python has a REPL but it's not the same as a .py; shell has no such gap.

# this works at the prompt
grep -r TODO . | wc -l

# …and unchanged inside todo-count.sh
07

Ecosystem — the whole shell family : ShellFamily

It's not just bash. The twelve below (shells + tools + main contexts) make up the real shell ecosystem in 2026. Every one is in real use: servers run bash, Mac desktops run zsh, Windows automation uses pwsh, and Dockerfiles / Actions default to bash.

08

vs Python / pwsh / Nushell : bash vs the rest

vs Python: shell for ≤ 100 lines of glue, Python for ≥ 100 lines of logic — the boundary is roughly the moment you want serious error handling. vs PowerShell: text streams vs object streams, two different worldviews. vs Nushell: tries to have both, but 35 years of install base is hard to beat.

BashPythonPowerShellNushell
OriginBrian Fox · 1989Guido · 1991Snover / MS · 2006Turner · 2019
Pipe payloadText streamsNone (function calls).NET objectsStructured tables / cols
TypesNone (all strings)Dynamic · optional typing.NET type systemStructured value types
Pre-installedevery Linux / macOS / WSLmost Linux · version chaosWindows default · elsewhere installableInstall required
Interactive vs scriptsame syntaxREPL ≠ .py formsame syntaxsame syntax
Error handlingset -euo pipefail · corner casestry / except · first-classtry / catch · ErrorRecordtry { ... } · structured errors
String quotingsix layers · word-split trapsA string is a stringtwo flavours · cleantwo flavours · clean
Ecosystem sizeevery *nix tool (find/sed/awk/...)PyPI · ~500k packagesPowerShell GallerySmall · early
Typical useCI / deploy / Dockerfile / one-offData / web / AI / businessWindows automation / AD / ExchangeData exploration / personal shell
When to leave it≥ 100 lines · real error handling · complex JSON/SQLDon'tCross-platform deployAny script you ship
09

Pitfalls & Reality : ShellTraps

Owning bash's bad parts beats pretending. Below: the four most-stepped-in traps + the perennial question ("is bash a programming language?"). Each one maps to a ShellCheck rule or a BashFAQ entry.

"

I've been maintaining bash for 30+ years. Every new feature gets me "shell shouldn't do that"; every old bug gets me "shell shouldn't have been designed that way". But every time I look at someone's GitHub Actions log or deploy script, it's still bash. It isn't a good language — it's an irreplaceable one. Those are different claims.

— Chet RameyBash maintainer 1992 → present · paraphrased from interviews
HOT · most common

Word splitting — unquoted variables explode

The shell splits variables on IFS (whitespace by default) then expands globs. Filenames with spaces, *, or ? turn into multiple arguments — silently.

Rule: quote every variable. "$f", "$@", "$${arr[@]}". There are almost no exceptions. ShellCheck's SC2086 catches every unquoted $var.

# BAD — file with spaces: silent disaster
rm $f

# GOOD
rm "$f"
TRAP

set -e doesn't catch what you think

set -e does not trigger in more places than you'd guess: inside conditions (if cmd; then), the left side of a pipe (without pipefail), cmd || echo bad, functions invoked with ||, and inside subshells. The corner-case list is long enough that Greg Wooledge's BashFAQ/105 exists to document them. Strict mode reduces failure surface; it doesn't eliminate it.

TRAP

Associative arrays need bash 4+, but macOS ships bash 3.2

bash 4.0 (2009) added associative arrays via declare -A, but macOS still ships bash 3.2 (2007 — because of GPLv3). On a Mac this trips up other people's scripts constantly: install Homebrew bash into /opt/homebrew/bin/bash and use #!/usr/bin/env bash. After Catalina (2019) Apple just made zsh the default and called it done.

IS BASH A LANG ?

Is bash a programming language?

Formally yes: Turing-complete, with variables, loops, functions, arrays, associative arrays, coroutines. By definition the answer is yes.

In practice: nobody writes more than 100 lines of bash without bugs. Variables are untyped, strings and numbers mix freely, quoting has six layers, and error handling depends on set -e's corner cases. The existence of BashFAQ and ShellCheck is itself the answer: it's a language that works but is not built for large programs.

In one line: In 2026 bash isn't the language you pick — it's the language you already use. Own it, run strict mode, use ShellCheck. Past 100 lines of nontrivial logic, switch to Python. That's honest shell engineering.