Getting Serious about Bash

A Rant

🔊
bashlogo 1

Bash is the fifth most-used programming language in the world, and the one that most programmers refuse to take seriously. This must change.


According to the 2025 Stack Overflow Developer Survey, 48.7% of developers worldwide use Bash. Not “have used at some point” – but use it, actively, as part of their working lives. That places Bash fifth overall, behind JavaScript, HTML/CSS, SQL, and Python; ahead of TypeScript. And the trajectory is striking: Bash jumped roughly fifteen percentage points in a single year, the largest year-on-year increase of any language in the survey.

I have been writing Bash professionally for over thirty years. Nearly half of all developers write Bash; and the overwhelming majority of them write it badly. I mean, really fucking badly.

Some stats: a 2022 study published in ACM Transactions on Software Engineering and Methodology analysed 1.35 million Bash scripts scraped from GitHub. The findings were pretty blunt: 80% of those scripts contained code smells. The most common problems were exactly what you would expect – quoting failures, word-splitting errors, missing error handling, sloppy structure. Basically, schoolboy errors.

This is the paradox. A mainstream language – used by more developers than TypeScript, running on 96.3% of the world’s top million web servers, present in every CI/CD (Continuous Integration / Continuous Delivery) pipeline and every container – treated with less engineering discipline than a weekend vibe coding project in Python.


Modern Bash Is Not What You Think

The objection I hear most often is that Bash is “just a shell”. A command-line interface, not a real language. This is usually said with a dismissive wave, as though the matter were settled decades ago and no further investigation is warranted. The people who say this are thinking of Bash circa 1995. They are not wrong about 1995; but they are badly wrong about now.

And just to be clear: I am not talking about sh, or the ancient Bash 3.2 beloved of macOS and blinkered Apple-tragics who have never questioned why their operating system ships a shell from 2007. And BSD? Still stuck on sh – no Bash at all; I mean, honestly, ffs. I am talking about modern Bash – 4.0 and above – the language as it actually exists on every Linux system built in the last fifteen years.

Bash 4.0, released in 2009, introduced associative arrays, coprocesses, and mapfile. Bash 4.3 added name references – declare -n – giving the language pointer-like indirection for the first time. Bash 5.0 brought epoch timestamps and improved nameref resolution. Bash 5.2, in 2022, rewrote the command substitution parser entirely, replacing ad-hoc parsing with a recursive bison grammar. And Bash 5.3, released in 2025, introduced no-fork command substitution – ${ cmd; } – which captures output without spawning a subshell. That is a genuine performance primitive.

Modern Bash has typed variable attributes via declare, strict error handling via set -euo pipefail, proper scoping with local, regular expression matching with =~, and signal handling through trap. It is unambiguously Turing-complete, and academic researchers at venues like ACM and POPL now treat it as a programming language worthy of formal analysis.

Bash has evolved, but sysadmins, programmers, and developers have not kept up.


Where Bash Actually Wins

The case for Bash is not sentimental; it’s practical, and the numbers are very specific. Bash starts in 2.8 milliseconds. Python takes 11.1 milliseconds – four times longer, and often much more. In a container image, a Bash environment occupies 12.5 megabytes. Python on Alpine takes 59.6 megabytes. When you are building minimal containers, deploying to resource-constrained environments, or running thousands of short-lived processes, these differences are not academic.

More importantly, Bash has zero dependencies. No runtime to install. No package manager to configure. No virtual environment to activate. No supply chain to audit for the language itself. Compare this with Python, where a simple deployment script can drag in pip, virtualenv, a requirements file with pinned versions, and a prayer that nothing conflicts with the system Python that half your other tools depend on. Anyone who has spent an afternoon debugging ModuleNotFoundError in a CI pipeline that worked yesterday knows exactly what dependency hell looks like. Bash does not have this problem. It does not have dependencies. It is the dependency.

In air-gapped networks, in regulated industries, in minimal containers where every megabyte counts – Bash is the language that is already present when nothing else is allowed to be. On 96.3% of the top million web servers, on 92% of cloud virtual machines, in every GitHub Actions runner, every GitLab CI executor, and every Jenkins agent – it is already there, waiting to be used.

And people do use it, at scale, for serious work. NVM, the Node Version Manager, has over 92,800 GitHub stars. It is written entirely in Bash. Acme.sh, the pure-shell ACME client for TLS certificates, has 46,200 stars. Pyenv, pi-hole, dokku – all Bash. These are not toys. They are critical infrastructure used by millions of developers daily.

The “right tool for the right job” argument actually favours Bash more than its critics admit. If you are orchestrating CLI commands, managing files, composing pipelines, running system health checks, or scripting container entrypoints, Bash is not just adequate – it is the native language of the problem domain. Wrapping aws, kubectl, and terraform in Python’s subprocess.run() adds complexity and indirection with no compensating benefit. The CLI was designed for the shell; use it!


The Real Problem

So if Bash is this capable, and this widely used, why is 80% of the code so damn poor?

The ACM study is instructive. The dominant code smells were not obscure corner cases. They were quoting errors and word-splitting mistakes – the absolute fundamentals of the language. This is not a failure of Bash. It is a failure of education, of standards, and of professional attitude.

Bash is the only mainstream language that most developers learn entirely by copying Stack Overflow answers. There is no university course on shell scripting. There is no onboarding module. There is no equivalent of Python’s PEP 8 or Go’s gofmt that new developers encounter in their first week. Nobody sits a junior engineer down and says, “Here is how you write a proper shell script.” They hand them a broken deployment pipeline and wish them luck.

And when programmers do seek out tutorials, what they find is worse than nothing; it’s simply atrocious. The top-ranked Bash tutorials on the web – freeCodeCamp, W3Schools, GeeksforGeeks, TutorialsPoint – uniformly teach unquoted variables, single-bracket conditionals, and scripts with no error handling whatsoever. Not one of them mentions set -euo pipefail.

The venerable TLDP Advanced Bash-Scripting Guide, which shaped a generation of shell scripters, still presents backtick substitution as a primary syntax, and has not been updated since 2014! On YouTube, channels with millions of subscribers teach scripts that silently swallow every error and continue blindly. These are not fringe resources. They are the front page of Google. They are where programmers learn Bash, and they are teaching it broken.

The result is cargo-cult scripting: patterns copied without understanding, errors propagated through generations of dumb tutorial sites, and a collective shrug when something breaks because “it’s just a script”.

These “shrugs” have consequences: On 30 October 2023, Cloudflare suffered a 37-minute outage affecting Workers KV – their globally distributed key-value store. The root cause was a deployment script that referenced the wrong environment. Not a complex distributed systems failure; not a subtle race condition, but a sloppy script that nobody reviewed with the same rigour they would have applied to a Python module.

“It’s just a script” may be the most expensive sentence in DevOps. Every CI/CD pipeline, every deployment workflow, every container entrypoint, every cron job – these are all “just scripts” until they take down production. The same developer who would never push an untested Python function will cheerfully commit a 200-line deployment script that has no error handling, no input validation, and no tests. They would not dream of skipping code review for a Go service. But the Bash that actually deploys that service? It gets a cursory glance at best.

The problem has never been the language. The problem is the attitude brought to the language. Fix the attitude, and the language performs exactly as well as any other.


Getting Serious

The tools exist. ShellCheck, with over 37,000 GitHub stars, is a static analyser that catches quoting errors, undefined variables, and common pitfalls before they reach production. Strict mode – set -euo pipefail – turns Bash from a language that silently ignores errors into one that fails fast and fails loudly. Testing frameworks like bats-core bring structured testing to shell scripts. Formatters like shfmt enforce consistent style.

And coding standards exist. We built one ourselves – the Bash Coding Standard – but the point is not the specific standard. The point is that the concept exists at all. That someone has sat down and codified what disciplined Bash looks like: how to declare variables, how to handle errors, how to structure a script, how to parse arguments. That the accumulated knowledge of decades of shell scripting has been organised into rules that can be taught, learned, checked, and enforced.

If you would not ship an unreviewed Python function to production, then you should not ship an unreviewed Bash script. If you would not write Go without go vet, do not ever write Bash without ShellCheck. If you would not deploy a Java service without tests, do not deploy a deployment script without tests. The double standard is indefensible, and it is costing you money and reliability every single week.

Bash is not going away. It is the fifth most-used language in the world and rising. It runs on virtually every server you own, and it is the connective tissue of your entire deployment infrastructure. It deserves engineering discipline. Give it some.

Gary Dean is the principal of Okusi Associates and co-founder of YaTTI (Indonesian Open Technology Foundation), and co-creator of the Bash Coding Standard.