The Shell Game Part 1: Introduction
I got my first look at Linux in 1992, on Slackware, and I’ve been a Bash user for essentially the entire time since. Debian became my distro of choice a long time ago and it’s stayed that way through every “have you tried Arch” conversation I’ve had, and Bash came along for the ride simply because it’s what Debian gives you and I’ve never had a reason to argue with it.
Lately that’s had a small dent put in it. I’ve been spending more time in Kali for pentest work, and Kali has shipped Zsh as its default shell since 2020.4. So for months now I’ve been alternating between a Bash prompt on my daily Debian boxes and a Zsh prompt on Kali without really thinking about it, which is exactly the kind of low-level inconsistency that eventually makes you stop and ask whether you’re missing something. Am I leaving useful functionality on the table by treating Zsh as “the thing Kali happens to run”, or is Bash still the right default for what I actually do all day: SSHing into other people’s infrastructure, scripting automation, and copy-pasting one-liners out of cheat sheets and write-ups?
That’s what this series is actually for. Not a shootout for its own sake, a real test of whether switching my daily interactive shell is worth the time, specifically for a networking-and-pentest-adjacent workflow rather than a general-purpose desktop one.
The lab
Everything in this series runs on a single dedicated VM rather than my daily-driver boxes, for the obvious reason that I didn’t want a botched Zsh plugin or a broken Fish config taking out a machine I actually rely on. It’s a plain Debian 13 guest, built the same way as the rest of the homelab, with Bash, Zsh, and Fish all installed side by side and switchable with chsh (or just launched directly by name, which is often easier when you’re jumping between all three in the same sitting). Every command shown in this series is a real run against that VM, not a transcription from documentation, in keeping with how I’ve tried to run every comparison series on this site.
The three shells, briefly
Bash (Bourne Again SHell) is the one that needs no introduction if you’ve touched Linux at all: it’s been the default on Debian, most other mainstream distros, and effectively every embedded Linux device and network appliance CLI wrapper you’ll ever SSH into. It’s a close, if imperfect, match for POSIX sh, which is precisely why almost every exploit one-liner, reverse shell payload, and blog-post script assumes it.
Zsh (Z Shell) is close enough to a superset of Bash syntax that most Bash scripts run under it unmodified, but it was built from the start with a much richer interactive layer: smarter completion, better globbing, and a genuinely large plugin ecosystem (Oh My Zsh, Prezto, and the rest) built around it. It’s been the macOS default since Catalina and the Kali default since 2020.4, so a lot of people are already using it without having consciously chosen it.
Fish (Friendly Interactive Shell) takes the opposite approach: excellent interactive behaviour out of the box, no plugin framework required, but it deliberately isn’t POSIX-compatible. Variable assignment, conditionals, and command substitution all use different syntax to Bash, which is either a breath of fresh air or a constant source of friction depending on how much of your day involves pasting commands written for something else.
Why “The Shell Game”
The obvious title for this series was something literal — “Bash vs Zsh vs Fish” does exactly what it says — but it undersells the actual shape of the thing. A shell game is a con: three cups, one pea, and the whole trick is that confident switching looks the same whether or not it’s actually moving anything useful underneath. That felt like an honest way to describe going in with an opinion already half-formed (Zsh’s the sensible middle ground, Fish is a novelty, Bash is safe-but-dull) and then having to actually prove it rather than just assert it. Whether that opinion survives four posts of real testing is the point of writing the series rather than just stating a conclusion here.
The lineup
| # | Post | Focus |
|---|---|---|
| 2 | Bash: the universal baseline | Regular tasks, where it’s genuinely weak interactively, why it stays the scripting default regardless of what wins the rest of this series |
| 3 | Zsh: same compatibility, better daily driver | Running the same tasks from Part 2 unmodified, then the completion, globbing, and plugin wins that justify the switch |
| 4 | Fish: great UX, wrong tool for this job | Where it genuinely shines out of the box, and a real, reproduced case of a pasted one-liner breaking under its syntax |
| 5 | Migrating from Bash to Zsh without losing anything | Porting existing dotfiles and aliases across the Debian fleet, and why scripts keep their #!/bin/bash shebang regardless of which shell you type into day to day |
Part 5 exists because a verdict without a migration path is just an opinion. If Part 3 makes the case for switching, Part 5 is what actually doing it looks like, including what happens to the shared .bash_aliases setup already running across the Debian hosts here.
What’s next
Part 2 starts with Bash, deliberately, because you can’t fairly judge what Zsh and Fish add until you’ve seen the baseline they’re both building on. Expect the same regular tasks — navigating history, tab completion, globbing, a bit of scripting — run for real, with the rough edges left in rather than smoothed over.
Cross-references: for the scripting side of Bash specifically, see Bash Patterns Every Network Engineer Should Know; for the SSH sessions this whole series lives inside, see the SSH Deep Dive series.