The Disappearing Script: Shebang Tricks and Magic Numbers
Open any script and the first line is probably #!/usr/bin/env bash. You’ve typed it so many times it’s muscle memory. The cursor lands, you type the shebang, and you move on.
But here’s a question worth pausing on: who actually reads that line?
Not the shell. Not Bash. The kernel.
The Magic Bytes
Ok, ok… that’s not completely true. If you run a script specifying the shell you want to use, as in:
File Descriptor Patterns: Beyond stdout and stderr
You’ve already used the STDIO redirection operators: <, >, 2>, 2>&1, and so on.
You may also have used the appending operators: >> and 2>>, and maybe the most modern ones: &> and &>>, to redirect both STDOUT and STDERR at the same time.
These operators cover most of what you need day-to-day. But have you ever wondered what they actually do — why > affects output, < affects input, and 2> affects errors? The answer is file descriptors, and understanding them turns a collection of punctuation into a coherent system.
The if Statement Secret: It Checks Exit Status, Not Booleans
Coming from almost any other programming language, you’d expect if to work on boolean expressions. You write something that evaluates to true or false, and the if decides which branch to take.
Bash works in a way that is almost, but not quite, entirely unlike that.
In Bash, if runs a command and checks its exit status. Zero means success (the “then” branch runs). Non-zero means failure (the “else” branch runs, if present).
NUL-Delimited Workflows: Handling Any Filename Safely
Pop quiz: what characters can appear in a filename on Linux?
If you answered “letters, numbers, dots, dashes, underscores,” you’re thinking of sensible filenames. The actual answer is: every character except / (path separator) and NUL (the zero byte).
That includes spaces. Tabs. Newlines. Quote marks. Backslashes. Asterisks. Emoji. Control characters. Bell characters that make your terminal beep. All perfectly legal in filenames.
And that’s a problem, because most UNIX tools assume newlines separate records. When a filename contains a newline, tools get confused:
Error-First Pattern: Writing Self-Documenting Bash
There’s a pattern in shell scripts that makes code harder to read than it needs to be. It looks like this:
if [[ -f "$config_file" ]]; then
if [[ -r "$config_file" ]]; then
source "$config_file"
if [[ -n "$DATABASE_URL" ]]; then
connect_to_database
if [[ $? -eq 0 ]]; then
run_migrations
# ... and so on
else
echo "Connection failed" >&2
exit 1
fi
else
echo "DATABASE_URL not set" >&2
exit 1
fi
else
echo "Config not readable" >&2
exit 1
fi
else
echo "Config not found" >&2
exit 1
fi
You’ve seen this. You’ve probably written this. I haven’t. Sorry, not sorry.
Have You Got the SKILLs? Teaching AI to Write Better Bash
— “Elmo’s got the moves” playing in my head since I typed this article’s title —
I wrote a book on Bash scripting that I’m about to publish. It teaches you how to write Bash, and more importantly, how to write it right.
But cool kids don’t write code anymore, do they?

Maybe the time will never come again when we’ll need to read and understand code ourselves—AI agents will have us covered. Maybe it’s fine. Maybe you don’t need to know the basics.
On Bash Testing: Design, Not Just Safety
Testing as a topic is a minefield already when it comes to full-fledged programming languages, let’s not even talk about it for shell scripting.
Or shall we?
You see, whether you write software to test other software, or you just fire up a script from the command line and keep running it over and over again until you nail it, that’s still testing, right?
Have you ever been woken up at night because a script failed six months after you wrote it? Or one year later? Or five years later? — For the love of my life, that thing was supposed to be temporary! There’s nothing more permanent than a temporary solution, they say.
Benchmarking Bash Scripts: Measuring Real Performance Improvements
Happy New Year! Let’s start 2026 with a look at something that surprises many developers: Bash script performance actually matters, and you can measure and improve it systematically.
If you’ve ever written Bash scripts that process thousands of log entries, iterate over file lists, or run in tight loops, you’ve probably wondered: “Is this as fast as it can be?” The answer is usually no—but the trick is knowing what to measure and how to interpret the results.
Designing Modular Bash: Functions, Namespaces, and Library Patterns
You’ve written Bash scripts before. Maybe you’ve automated a deployment, wrangled some log files, or stitched together a few commands to save time. But as your scripts grow, something changes. What started as a clean 20-line helper becomes a 200-line sprawl. Functions call functions that modify global variables. You copy-paste code between scripts because extracting shared logic feels harder than it should be. The script works, but it’s fragile, and you’re the only person who can maintain it.