At Apple’s recommendation I’ve moved from Bash to Zsh on all my Macs. This has been a mostly smooth transition, but I have run into a handful of little gotchas. This week it was an error when trying to execute a command I’d been using in Bash for years. The error started zsh: no matches found.

The cause of this error is a subtle but important difference in how Bash and Zsh handle file globbing (expansion of the * character) when no files match the specified pattern. By default, Bash happily expands expressions that don’t match anything into an empty list. Zsh’s default behaviour is to raise an error and prevent the command executing.

TL;DRsetopt NULL_GLOB to enable Bash-like behaviour, and unsetopt NULL_GLOB to revert back to the Zsh-like behaviour.

You can quickly and easily see the difference of behaviour by setting up a test folder with a few files in it like so:

This will create a folder named globDemo, change into it, and then create the following (empty) files:

app1.js
index.html
logo.png
main.css
main.js

This is a realistic set of files for a very basic web app. You might also realistically have a ZIP command for archiving these files that you somehow save and regularly re-use (perhaps as a TextExpander snippet). That command could try to archive all files of the common web types into a single file. The following would be reasonable:

This command tries to archive all HTML, CSS, JavaScript, PNG & JPEG files into a ZIP file named webArchive.zip. Some sites will have files of all of those types, but others won’t. That means that sometimes some of those * expressions (file globs) will not match any files.

In our demo folder there are HTML, CSS, JavaScript, and PNG files, but no JPEG files. If we run the ZIP command in Bash within our demo folder it gives a warning to let us know one of our globs didn’t match anything, but the command still gets executed and it works just fine:

So what happens when we run exactly the same command in Zsh?

As you can see, we get the error zsh: no matches found: *.jpeg because our folder doesn’t contain any JPEG files. Unlike the warning form Bash, this is an error, so the command does not execute, and the ZIP does not get created.

Zsh’s NULL_GLOB Option

What Zsh does when a glob doesn’t expand to anything is controlled by the NULL_GLOB option. This option is un-set by default. You can set it with the command setopt NULL_GLOB, and you can un-set it with unsetopt NULL_GLOB.

If you set this option then Zsh behaves more like Bash, though without showing an explicit warning:

A Permanent Fix?

You could choose to always enable this behaviour by adding the the setopt command into your ~/.zshrc file:

# don't throw errors when file globs don't match anything
setopt NULL_GLOB

I’d suggest not doing this because the Zsh people didn’t make this change arbitrarily, they put a lot of thought into it, and decided that on balance, the new behaviour is safer. Many commands won’t do what you expect if a glob expands to an empty list. Also, it’s reasonable to assume that most of the time, we expect our globs to expand out to at least one file, so if they don’t something probably went wrong!

By far the simplest and least-invasive solution is to update any saved commands so they are wrapped by a setopt and an unsetopt. I.e., we would update our example command as follows:

An Interesting Middle-ground — CSH_NULL_GLOB

The C Shell (csh) takes an interesting alternative approach to both Bash and Zsh — it will throw an error like Zsh, but only of all the globs in a given command return an empty list. This prevents empty lists causing un-expected behaviour, and, it would not throw an error in our example because all the globs other than *.jpeg do match at least one file.

Thankfully the Zsh developers were paying attention, and they have provided an option for enabling C Shell-like behaviour in Zsh! The option is pretty well named too — CSH_NULL_GLOB. As you might expect, you can enable this option with the command setopt CSH_NULL_GLOB:

If you prefer not to have to wrap commands like suggested above, perhaps adding the following to your ~/.zshrc might be a good alternative:

# only throw errors when no globs match anything
setopt CSH_NULL_GLOB