{"id":576,"date":"2017-04-22T20:43:42","date_gmt":"2017-04-22T20:43:42","guid":{"rendered":"http:\/\/www.vidarholen.net\/contents\/blog\/?p=576"},"modified":"2017-04-22T20:43:42","modified_gmt":"2017-04-22T20:43:42","slug":"z-var-works-unreasonably-well","status":"publish","type":"post","link":"https:\/\/www.vidarholen.net\/contents\/blog\/?p=576","title":{"rendered":"<code>[ -z $var ]<\/code> works unreasonably well"},"content":{"rendered":"<p>There is a subreddit <a href=\"https:\/\/www.reddit.com\/r\/nonononoyes\/top\/?sort=top&#038;t=all\">\/r\/nononoyes<\/a> for videos of things that look like they&#8217;ll go horribly wrong, but amazingly turn out ok.<\/p>\n<p><code>[ -z $var ]<\/code> would belong there.<\/p>\n<p>It&#8217;s a bash statement that tries to check whether the variable is empty, but it&#8217;s missing quotes. Most of the time, when dealing with variables that can be empty, this is a disaster. <\/p>\n<p>Consider its opposite, <code>[ -n $var ]<\/code>, for checking whether the variable is non-empty. With the same quoting bug, it becomes completely unusable:<\/p>\n<table>\n<tr>\n<th>Input<\/th>\n<th>Expected<\/th>\n<th><code>[ -n $var ]<\/code><\/th>\n<\/tr>\n<tr>\n<td>&#8220;&#8221;<\/td>\n<td>False<\/td>\n<td><strong style=\"color:red\">True!<\/strong><\/td>\n<\/tr>\n<tr>\n<td>&#8220;foo&#8221;<\/td>\n<td>True<\/td>\n<td><strong style=\"color:green\">True<\/strong><\/td>\n<\/tr>\n<tr>\n<td>&#8220;foo bar&#8221;<\/td>\n<td>True<\/td>\n<td><strong style=\"color:red\">False!<\/strong><\/td>\n<\/tr>\n<\/table>\n<p>These issues are due to a combination of word splitting and the fact that <code>[<\/code> is not shell syntax but traditionally just an external binary with a funny name. See my previous post <a href=\"\/contents\/blog\/?p=25\">Why Bash is like that: Pseudo-syntax<\/a> for more on that.<\/p>\n<p>The evaluation of <code>[<\/code> is defined in terms of the number of argument. The argument values have much less to do with it. Ignoring negation, here&#8217;s a simplified excerpt from <a href=\"http:\/\/pubs.opengroup.org\/onlinepubs\/9699919799\/utilities\/test.html\">POSIX test<\/a>:<\/p>\n<table>\n<tr>\n<th># Arguments<\/th>\n<th>Action<\/th>\n<th>Typical example<\/th>\n<\/tr>\n<tr>\n<td>0<\/td>\n<td>False<\/td>\n<td><code>[ ]<\/code><\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>True if $1 is non-empty<\/td>\n<td><code>[ \"$var\" ]<\/code><\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>Apply unary operator $1 to $2<\/td>\n<td><code>[ -x \"\/bin\/ls\" ]<\/code><\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>Apply binary operator $2 to $1 and $3<\/td>\n<td><code>[ 1 -lt 2 ]<\/code><\/td>\n<\/tr>\n<\/table>\n<p>Now we can see why <code>[ -n $var ]<\/code> fails in two cases:<\/p>\n<p>When the variable is empty and unquoted, it&#8217;s removed, and we pass 1 argument: the literal string &#8220;-n&#8221;. Since &#8220;-n&#8221; is not an empty string, it evaluates to true when it should be false.<\/p>\n<p>When the variable contains <code>foo bar<\/code> and is unquoted, it&#8217;s split into two arguments, and so we pass 3: &#8220;-n&#8221;, &#8220;foo&#8221; and &#8220;bar&#8221;. Since &#8220;foo&#8221; is not a binary operator, it evaluates to false (with an error message) when it should be true.<\/p>\n<p>Now let&#8217;s have a look at <code>[ -z $var ]<\/code>:<\/p>\n<table>\n<tr>\n<th>Input<\/th>\n<th>Expected<\/th>\n<th><code>[ -z $var ]<\/code><\/th>\n<th>Actual test<\/th>\n<\/tr>\n<tr>\n<td>&#8220;&#8221;<\/td>\n<td>True: is empty<\/td>\n<td><strong style=\"color:green\">True<\/strong><\/td>\n<td>1 arg: is &#8220;-z&#8221; non-empty<\/td>\n<\/tr>\n<tr>\n<td>&#8220;foo&#8221;<\/td>\n<td>False: not empty<\/td>\n<td><strong style=\"color:green\">False<\/strong><\/td>\n<td>2 args: apply <code>-z<\/code> to foo<\/td>\n<\/tr>\n<tr>\n<td>&#8220;foo bar&#8221;<\/td>\n<td>False: not empty<\/td>\n<td><strong style=\"color:green\">False<\/strong> (error)<\/td>\n<td>3 args: apply &#8220;foo&#8217; to -z and bar<\/td>\n<\/tr>\n<\/table>\n<p>It performs a completely wrong and unexpected action for both empty strings and multiple arguments. However, both cases fail in exactly the right way!<\/p>\n<p>In other words, <code>[ -z $var ]<\/code> works way better than it has any possible business doing.<\/p>\n<p>This is not to say you can skip quoting of course. For &#8220;foo bar&#8221;, <code>[ -z $var ]<\/code> in bash will return the correct exit code, but prints an ugly error in the process. For &#8221;    &#8221; (a string with only spaces), it returns true when it should be false, because the argument is removed as if empty. Bash will also incorrectly pass  <code>var=\"foo -o x\"<\/code> because it ends up being a valid test through code injection. <\/p>\n<p>The moral of the story? Same as always: quote, quote quote. Even when things appear to work. <\/p>\n<p>ShellCheck is aware of this difference, and you can <a href=\"http:\/\/www.shellcheck.net\/?id=testz\">check the code used here online<\/a>. <code>[ -n $var ]<\/code> gets an angry red message, while <code>[ -z $var ]<\/code> merely gets a generic green quoting warning.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There is a subreddit \/r\/nononoyes for videos of things that look like they&#8217;ll go horribly wrong, but amazingly turn out ok. [ -z $var ] would belong there. It&#8217;s a bash statement that tries to check whether the variable is empty, but it&#8217;s missing quotes. Most of the time, when dealing with variables that can &hellip; <a href=\"https:\/\/www.vidarholen.net\/contents\/blog\/?p=576\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;<code>[ -z $var ]<\/code> works unreasonably well&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[6,4,23],"tags":[11,21],"class_list":["post-576","post","type-post","status-publish","format-standard","hentry","category-basic-linux","category-linux","category-programming","tag-bash","tag-shell-script"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/posts\/576","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=576"}],"version-history":[{"count":36,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/posts\/576\/revisions"}],"predecessor-version":[{"id":661,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=\/wp\/v2\/posts\/576\/revisions\/661"}],"wp:attachment":[{"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=576"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=576"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vidarholen.net\/contents\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=576"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}