bash,  Mac OS X

Differences Between zsh and bash

Supposedly, macOS 10.15 Catalina is slated to replace the default /bin/bash shell with zsh, or /bin/zsh. Before we talk about the differences let’s just say that bash is still here and if your script is called as bash then it will still work just fine. To quickly see which you’re using (e.g. when testing a new release), use $0:

echo $0

Z Shell or zsh for short was written by Princeton University student Paul Falstad in 1990. Most shells are just extensions of the Bourne shell (including bash) and work similarly but there are minor differences here and there. Yes, Z Shell comes with a control-R reverse incremental search, but that’s not a good reason to make this kind of change. Z Shell is more modern (e.g. more customizable autocompletion, use Alt + . to put parameters from the previous command into your next command, slicker tabbed auto-complete), considered by some to be more secure (not considered as such by others), and, well, it doesn’t make you wonder what the next Jason Bourne movie will be about (he invented bash, right?!?!).

So let’s go through some of the newish things to get used to. These include (but are not limited to) the following:

File globbing: Let’s take something like using an * to list file contents. So files=* in bash:

somefiles=*

Then let’s read the contents of that $file variable:

echo $somefiles

The output would be as follows, a basic list of files:


About This Mac.app Archive Utility.app DVD Player.app Directory Utility.app Feedback Assistant.app Folder Actions Setup.app Network Utility.app RAID Utility.app Screen Sharing.app Storage Management.app System Image Utility.app Wireless Diagnostics.app

Now let’s do the same operation in zsh:

somefiles=*

And then we’ll echo it:

echo $somefiles

The output shows that the * was accepted literally:

*

So to get the same result I often wrap my globs in a () like the following (which includes two to trap for hidden directories):

somefiles=(*(N))

The security benefit here is that you don’t accidentally include something you’re not supposed to while getting more options for dealing with expansion. You can do this the same old way by enabling globsubst.

Directory Aliases: The next big difference would be directory aliases. The alias command in zsh allows for expanded aliases anywhere in a line. To put this in context, let’s grep output to something with an alias:

alias -g GS="| grep something"

Then:

cat somefile GS

Next on my list would be environment scripts. These include:

  • zlogin
    zlogout
  • zprofile
  • zshenv
  • zshrc

Additionally, zsh does spelling correction, which I’m personally not used to.

Some scripty bits to look out for with zsh:

  • Don’t set BASH_ENV (obviously), ENV, or SHELL the same
  • exec changes (see http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html for more on how zsh does this)
  • Native hashed data structure support in zsh using typeset
  • No -x like in bash
  • Remove any PROMPT_COMMAND entries
  • Replace any calls to getopts with zparseopts
  • SHELLOPTS isn’t run at startup
  • Use zcalc for all the maths including floating point support not present in bash natively: autoload -Uz zcalc
  • -norc doesn’t skip anything
  • -rcfile calls

There’s a zmv extension that can be loaded using

autoload -U zmv

This is because zsh is also modular. So you can load modules that help, for example, do additional file manipulation commands (zsh/files), use posix regex (zsh/regex) or deal with sockets (zsh/net/socket). Or to view a good list of plugins, see https://github.com/unixorn/awesome-zsh-plugins. Oh there are bundles as well. You still awake?

If so, what do you lose? Honestly not a lot. Bash restricted mode, which I’ve only very rarely used:

bash --restricted

And no posix mode:

bash -o posix

Again, also not used much…

In general, when scripting automation for the Mac, I’d say obviously test all your scripts – especially if you aren’t specifically invoking them through /bin/bash. Many of these changes won’t be very impactful. Maybe you’ll have to get used to something working just a tiny bit differently when you’re interactively navigating through the shell; no big deal. But for scripting, definitely consider the globbing whatnot as something to look out for and know that if it breaks a script, rather than just calling bash to run, think about moving it over to the new default shell. Because it’s this way for a reason. And that reason probably isn’t just that some developer didn’t like the story or acting in the Bourne Legacy (although it might have been that bad)…