{"id":15872,"date":"2019-06-12T15:15:39","date_gmt":"2019-06-12T15:15:39","guid":{"rendered":"https:\/\/www.bartbusschots.ie\/s\/?p=15872"},"modified":"2019-06-17T14:28:02","modified_gmt":"2019-06-17T14:28:02","slug":"bash-to-zsh-file-globbing-and-no-matches-found-errors","status":"publish","type":"post","link":"https:\/\/www.bartbusschots.ie\/s\/2019\/06\/12\/bash-to-zsh-file-globbing-and-no-matches-found-errors\/","title":{"rendered":"Bash to Zsh: File Globbing and &#8216;no matches found&#8217; Errors"},"content":{"rendered":"<div class=\"pps-series-post-details pps-series-post-details-variant-classic pps-series-post-details-18702\" data-series-id=\"581\"><div class=\"pps-series-meta-content\"><div class=\"pps-series-meta-text\">This entry is part 3 of 6 in the series <a href=\"https:\/\/www.bartbusschots.ie\/s\/series\/bash-to-zsh\/\">Bash to Zsh<\/a><\/div><\/div><\/div><p>At <a href=\"https:\/\/support.apple.com\/en-us\/HT208050\" rel=\"noopener noreferrer\" target=\"_blank\">Apple&#8217;s recommendation<\/a> I&#8217;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 <em>gotchas<\/em>. This week it was an error when trying to execute a command I&#8217;d been using in Bash for years. The error started <code>zsh: no matches found<\/code>.<\/p>\n<p>The cause of this error is a subtle but important difference in how Bash and Zsh handle file globbing (expansion of the <code>*<\/code> character) when no files match the specified pattern. By default, Bash happily expands expressions that don&#8217;t match anything into an empty list. Zsh&#8217;s default behaviour is to raise an error and prevent the command executing.<\/p>\n<p><strong>TL;DR<\/strong> \u00e2\u20ac\u201d <code>setopt NULL_GLOB<\/code> to enable Bash-like behaviour, and <code>unsetopt NULL_GLOB<\/code> to revert back to the Zsh-like behaviour.<\/p>\n<p><!--more--><\/p>\n<p>You can quickly and easily see the difference of behaviour by setting up a test folder with a few files in it like so:<\/p>\n<pre class=\"lang:sh decode:true \" >\r\nmkdir globDemo\r\ncd globDemo\r\ntouch index.html app1.js main.js main.css logo.png\r\n<\/pre>\n<p>This will create a folder named <code>globDemo<\/code>, change into it, and then create the following (empty) files:<\/p>\n<pre class=\"crayon:false\">\r\napp1.js\r\nindex.html\r\nlogo.png\r\nmain.css\r\nmain.js\r\n<\/pre>\n<p>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:<\/p>\n<pre class=\"lang:sh decode:true \" >\r\nzip webArchive.zip *.html *.css *.js *.png *.jpeg\r\n<\/pre>\n<p>This command tries to archive all HTML, CSS, JavaScript, PNG &#038; JPEG files into a ZIP file named <code>webArchive.zip<\/code>. Some sites will have files of all of those types, but others won&#8217;t. That means that sometimes some of those <code>*<\/code> expressions (file globs) will not match any files.<\/p>\n<p>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&#8217;t match anything, but the command still gets executed and it works just fine:<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/www.bartbusschots.ie\/s\/wp-content\/uploads\/2019\/06\/Screenshot-2019-06-12-at-12.41.45.png\" alt=\"\" width=\"480\" class=\"alignnone size-full wp-image-15873\" style=\"border-width: 0px;\" \/><\/p>\n<p>So what happens when we run exactly the same command in Zsh? <\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/www.bartbusschots.ie\/s\/wp-content\/uploads\/2019\/06\/Screenshot-2019-06-12-at-12.53.00.png\" alt=\"\" width=\"480\" class=\"alignnone size-full wp-image-15874\" style=\"border-width: 0px;\" \/><\/p>\n<p>As you can see, we get the error <code>zsh: no matches found: *.jpeg<\/code> because our folder doesn&#8217;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.<\/p>\n<h2>Zsh&#8217;s <code>NULL_GLOB<\/code> Option<\/h2>\n<p>What Zsh does when a glob doesn&#8217;t expand to anything is controlled by the <code>NULL_GLOB<\/code> option. This option is un-set by default. You can set it with the command <code>setopt NULL_GLOB<\/code>, and you can un-set it with <code>unsetopt NULL_GLOB<\/code>.<\/p>\n<p>If you set this option then Zsh behaves more like Bash, though without showing an explicit warning:<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/www.bartbusschots.ie\/s\/wp-content\/uploads\/2019\/06\/Screenshot-2019-06-12-at-13.00.34.png\" alt=\"\" width=\"480\" class=\"alignnone size-full wp-image-15875\" style=\"border-width: 0px;\" \/><\/p>\n<h2>A Permanent Fix?<\/h2>\n<p>You could choose to always enable this behaviour by adding the the <code>setopt<\/code> command into your <code>~\/.zshrc<\/code> file:<\/p>\n<pre class=\"crayon:false\">\r\n# don't throw errors when file globs don't match anything\r\nsetopt NULL_GLOB\r\n<\/pre>\n<p>I&#8217;d suggest not doing this because the Zsh people didn&#8217;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&#8217;t do what you expect if a glob expands to an empty list. Also, it&#8217;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&#8217;t something probably went wrong!<\/p>\n<p>By far the simplest and least-invasive solution is to update any saved commands so they are wrapped by a <code>setopt<\/code> and an <code>unsetopt<\/code>. I.e., we would update our example command as follows:<\/p>\n<pre class=\"lang:sh decode:true \" >\r\nsetopt NULL_GLOB; zip webArchive.zip *.html *.css *.js *.png *.jpeg; unsetopt NULL_GLOB\r\n<\/pre>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/www.bartbusschots.ie\/s\/wp-content\/uploads\/2019\/06\/Screenshot-2019-06-12-at-13.22.51.png\" alt=\"\" width=\"480\" class=\"alignnone size-full wp-image-15877\" style=\"border-width: 0px;\" \/><\/p>\n<h2>An Interesting Middle-ground \u00e2\u20ac\u201d <code>CSH_NULL_GLOB<\/code><\/h2>\n<p>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/C_shell\" rel=\"noopener noreferrer\" target=\"_blank\">C Shell<\/a> (csh) takes an interesting alternative approach to both Bash and Zsh \u00e2\u20ac\u201d it will throw an error like Zsh, but only of <strong>all<\/strong> 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 <code>*.jpeg<\/code> do match at least one file.<\/p>\n<p>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 \u00e2\u20ac\u201d <code>CSH_NULL_GLOB<\/code>. As you might expect, you can enable this option with the command <code>setopt CSH_NULL_GLOB<\/code>:<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" src=\"https:\/\/www.bartbusschots.ie\/s\/wp-content\/uploads\/2019\/06\/Screenshot-2019-06-12-at-13.33.10.png\" alt=\"\" width=\"480\" class=\"alignnone size-full wp-image-15878\" style=\"border-width: 0px;\" \/><\/p>\n<p>If you prefer not to have to wrap commands like suggested above, perhaps adding the following to your <code>~\/.zshrc<\/code> might be a good alternative:<\/p>\n<pre class=\"crayon:false\">\r\n# only throw errors when no globs match anything\r\nsetopt CSH_NULL_GLOB\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<div class=\"pps-series-post-details pps-series-post-details-variant-classic pps-series-post-details-18702 pps-series-meta-excerpt\" data-series-id=\"581\"><div class=\"pps-series-meta-content\"><div class=\"pps-series-meta-text\">This entry is part 3 of 6 in the series <a href=\"https:\/\/www.bartbusschots.ie\/s\/series\/bash-to-zsh\/\">Bash to Zsh<\/a><\/div><\/div><\/div><p>At Apple&#8217;s recommendation I&#8217;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&#8217;d been using in Bash for years. The error started zsh: no matches [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[12,446],"tags":[406,458,580],"series":[581],"class_list":["post-15872","post","type-post","status-publish","format-standard","hentry","category-computers-tech","category-sysadmin","tag-commandline","tag-tutorial","tag-zsh","series-bash-to-zsh"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7t9xK-480","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/posts\/15872","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/comments?post=15872"}],"version-history":[{"count":4,"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/posts\/15872\/revisions"}],"predecessor-version":[{"id":15883,"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/posts\/15872\/revisions\/15883"}],"wp:attachment":[{"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/media?parent=15872"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/categories?post=15872"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/tags?post=15872"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/www.bartbusschots.ie\/s\/wp-json\/wp\/v2\/series?post=15872"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}