The Shell Game Part 3: Zsh, Same Compatibility, Better Daily Driver

Part 2 established Bash’s baseline on debian1. This post installs Zsh alongside it and runs the same tests again, unmodified, before getting into what Zsh actually adds once it’s configured.

First launch: the wizard Bash never shows you

With no ~/.zshrc present, Zsh’s first interactive launch drops straight into a configuration wizard rather than a plain prompt:

This is the Z Shell configuration function for new users,
zsh-newuser-install.
You are seeing this message because you have no zsh startup files
(the files .zshenv, .zprofile, .zshrc, .zlogin in the directory
~).  This function can help you with a few settings that should
make your use of the shell easier.

You can:

(q)  Quit and do nothing.  The function will be run again next time.

(0)  Exit, creating the file ~/.zshrc containing just a comment.
     That will prevent this function being run again.

(1)  Continue to the main menu.

(2)  Populate your ~/.zshrc with the configuration recommended
     by the system administrator and exit (you will need to edit
     the file by hand, if so desired).

Option 0 gets you a genuinely unconfigured Zsh — an empty .zshrc with just a comment in it — which is what the retest below actually ran against.

The raw prompt is a surprise

$ echo $PS1
%m%#

That’s the entire default: hostname and a prompt character, no colour, no username, no path. It’s worth stating plainly because it cuts against the easy assumption that Zsh is simply “the nicer one” out of the box — Bash’s default two-colour user@host:path$ prompt from Part 2 is actually more informative than this. Whatever Zsh is offering here, it isn’t a prettier starting point.

Two independent hard errors Bash would have shrugged off

The first came from a mistake in my own test instructions that accidentally became a real finding. The second is the same thing, deliberately reproduced:

# bash
$ echo done #nothing?
done

# zsh
$ echo done #nothing?
zsh: no matches found: #nothing?

Two things are happening at once. Zsh doesn’t treat # as starting a comment in an interactive shell by default — that needs setopt interactive_comments — so #nothing? gets parsed as a real argument rather than discarded. And nothing? contains an unquoted ?, a glob character with no matching file, which Zsh’s default NOMATCH behaviour turns into a hard error rather than passing through literally the way Bash does.

That second half showed up completely independently, too:

# bash (Part 2)
$ ls *.conf
ls: cannot access '*.conf': No such file or directory

# zsh
$ ls *.conf
zsh: no matches found: *.conf

Same root cause, same result, from two unrelated commands run at different points in testing. Bash hands the unmatched pattern to ls, which reports the OS-level “file not found.” Zsh refuses to run ls at all, because it caught the failed glob itself first. Neither is wrong, but it’s a real behavioural difference that will bite you the first time you type a stray ? or * into a Zsh prompt expecting it to just pass through.

Globbing: ** recurses with nothing switched on

Reusing Part 2’s globtest directory, with one addition — a file two levels deep this time, to actually test recursion rather than assume it:

$ mkdir -p ~/globtest/a/deeper
$ touch ~/globtest/a/deeper/four.conf
$ cd ~/globtest
$ ls */*.conf
a/one.conf  b/two.conf
$ ls **/*.conf
a/deeper/four.conf  a/one.conf  b/two.conf

*/*.conf stops at one level down, same as Bash, and misses the new file entirely. **/*.conf finds it, with no setopt required — this is core Zsh glob behaviour, not an opt-in the way globstar is in Bash. That’s a genuine capability gap in Bash’s favour of Zsh, not a marginal one.

The compatibility test: Part 2’s script, unmodified

#!/usr/bin/env zsh
for host in 1.1.1.1 8.8.8.8 192.0.2.1; do
  if ping -c 1 -W 1 "$host" >/dev/null 2>&1; then
    echo "$host: UP"
  else
    echo "$host: DOWN"
  fi
done
$ ./checkhosts_zsh.sh
1.1.1.1: UP
8.8.8.8: UP
192.0.2.1: DOWN

Identical output to Part 2, only the shebang changed. The for/if control-flow syntax that’s often assumed to be “Bash syntax” is really POSIX-ish shell syntax that Zsh parses the same way.

The real test: a years-old .bash_aliases file, unmodified

Scripts are one thing; the file you’ve actually built up over years of daily use is the test that matters. Sourcing it as-is:

$ echo '[[ -f ~/.bash_aliases ]] && source ~/.bash_aliases' >> ~/.zshrc
$ exec zsh
$ type mnt
mnt is an alias for mount | awk -F' ' '{ printf "%s\t%s\n",$1,$3; }' | column -t | egrep ^/dev/ | sort
$ mnt
/dev/sda1    /
$ type path
path is an alias for echo -e ${PATH//:/\\n}
$ path
/usr/local/bin
/usr/bin
/bin
/usr/local/games
/usr/games
$ ipbr
lo               UNKNOWN        127.0.0.1/8
ens18            UP             192.168.11.7/24
$ alias rm
rm='rm -I --preserve-root'

Every one of those ported without a single change: a quoted awk/column/egrep pipeline, Bash’s ${PATH//:/\\n} global search-and-replace parameter expansion (which Zsh inherited from the same ksh lineage Bash did), and the plain safety aliases. If you’ve got years of muscle memory built into a .bash_aliases file, this is the actual answer to “will I lose any of it” — no.

The productivity layer

$ sudo apt install -y zsh-autosuggestions zsh-syntax-highlighting
$ echo "source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh" >> ~/.zshrc
$ echo "source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" >> ~/.zshrc
$ echo "autoload -Uz compinit && compinit" >> ~/.zshrc
$ exec zsh

Two Debian packages and three lines in .zshrc, no plugin manager, no framework.

Typing "ech" with the rest of a previous command shown as greyed autosuggestion text, and the completed line above with the command coloured green and the quoted string coloured orange, against the adam1 prompt's blue user@host block

That one screenshot shows three separate features working together but doing different jobs. zsh-syntax-highlighting colours the command line live as you type — ech sits in red because it isn’t a recognised command yet, echo turns green the moment it is, and the quoted string gets its own colour once the line runs. zsh-autosuggestions is the greyed-out text trailing the cursor, pulled straight from history, ready to accept with a right-arrow. And the blue background block behind anon@debian1 is the prompt theme from the next section, already in place. None of this exists in Bash without real configuration work; all of it comes from two apt install commands and the prompt theme below.

Completion itself is a more honest story. Re-running Part 2’s exact test post-compinit:

$ sudo apt install fire<TAB>
firebird3.0-common            firefox-esr-l10n-bs           firefox-esr-l10n-gn ...

Still a plain, uncoloured wall of text — functionally identical to Bash’s bash-completion output from Part 2, just without the (y or n) gate. Syntax highlighting improved the command line; it did nothing to the completion menu. Getting an actual colourised, fuzzy-matched completion experience needs further zstyle configuration, which is outside what “install two packages” gets you — worth being precise about rather than implying the productivity layer above is more complete than it is.

A prompt system with no plugin manager

$ autoload -Uz promptinit && promptinit
$ prompt -l
Currently available prompt themes:
adam1 adam2 bart bigfade clint default elite2 elite fade fire off oliver pws redhat restore suse walters zefram
$ prompt adam1
$ echo $PS1
%K{blue}%n@%m%k %B%F{cyan}%(4~|...|)%3~%F{white} %# %b%f%k

Eighteen bundled themes, all shipped with Zsh itself. adam1 gets you a coloured background block for user@host and a bold, truncating path segment (%(4~|...|)%3~ — show the last three path components, with ... standing in once you’re more than four deep) in one command, no external theme engine required. That’s the blue bar visible in the screenshot above.

Where this leaves things

Every compatibility question this post set out to answer came back yes: the script from Part 2, the real alias file, quoting, pipes, parameter expansion, all unmodified. Two genuine behavioural differences showed up along the way — Zsh hard-errors on unmatched globs where Bash passes them through, and ** recurses by default where Bash needs globstar switched on — neither of which breaks anything, both worth knowing before they surprise you mid-session. And the productivity case holds up under its own scrutiny: two apt install commands and a few lines in .zshrc get you live syntax highlighting, history-based autosuggestions, and a proper prompt theme system, none of which Bash offers without real configuration work of your own. What it doesn’t get you, on its own, is a genuinely better completion experience — that’s still plain text here, same as Bash’s.

What’s next

Part 4 installs Fish on the same box and runs a version of these same tests — with one important difference. Fish isn’t POSIX-compatible, so the compatibility half of this post doesn’t get a fair retest the same way. What it gets instead is a real, reproduced case of exactly where that breaks.