Jun
12
Bash to Zsh: File Globbing and ‘no matches found’ Errors
Filed Under Computers & Tech, System Administration on June 12, 2019 at 3:15 pm
- Moving from Bash to Zsh on macOS
- Getting a Bash-like Prompt in Zsh
- Bash to Zsh: File Globbing and ‘no matches found’ Errors
- Getting Back to Bash from Zsh on MacOS
- Bash to Zsh: Allowing Comments in Interactive Shells
- Moving my .bashrc to .zshrc
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;DR — setopt 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:
mkdir globDemo cd globDemo touch index.html app1.js main.js main.css logo.png
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:
zip webArchive.zip *.html *.css *.js *.png *.jpeg
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:
setopt NULL_GLOB; zip webArchive.zip *.html *.css *.js *.png *.jpeg; unsetopt NULL_GLOB
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