81

I know I can test for an empty string in Bash with -z like so:

if [[ -z $myvar ]]; then do_stuff; fi

but I see a lot of code written like:

if [[ X"" = X"$myvar" ]]; then do_stuff; fi

Is that method more portable? Is it just historical cruft from before the days of -z? Is it for POSIX shells (even though I've seen it used in scripts targeting bash)? Ready for my history/portability lesson.


The same question was asked on Server Fault as How to determine if a bash variable is empty? but no one offered an explanation as to why you see code with the X"" stuff.

codeforester
  • 39,467
  • 16
  • 112
  • 140
mgalgs
  • 15,671
  • 11
  • 61
  • 74
  • 1
    Great question btw. This post has a good answer [serverfault post](http://serverfault.com/questions/7503/how-to-determine-if-a-bash-variable-is-empty) – chrislovecnm Jul 27 '11 at 23:56

2 Answers2

127

Fundamentally, because in times now long past, the behaviour of test was more complex and not uniformly defined across different systems (so portable code had to be written carefully to avoid non-portable constructs).

In particular, before test was a shell built-in, it was a separate executable (and note that MacOS X still has /bin/test and /bin/[ as executables). When that was the case, writing:

if [ -z $variable ]

when $variable was empty would invoke the test program via its alias [ with 3 arguments:

argv[0] = "["
argv[1] = "-z"
argv[2] = "]"

because the variable was empty so there was nothing to expand. So, the safe way of writing the code was:

if [ -z "$variable" ]

This works reliably, passing 4 arguments to the test executable. Granted, the test program has been a built-in to most shells for decades, but old equipment dies hard, and so do good practices learned even longer ago.

The other problem resolved by the X prefix was what happened if variables include leading dashes, or contain equals or other comparators. Consider (a not desparately good example):

x="-z"
if [ $x -eq 0 ]

Is that an empty string test with a stray (erroneous) argument, or a numeric equality test with a non-numeric first argument? Different systems provided different answers before POSIX standardized the behaviour, circa 1990. So, the safe way of dealing with this was:

if [ "X$x" = "X0" ]

or (less usually, in my experience, but completely equivalently):

if [ X"$x" = X"0" ]

It was all the edge cases like this, tied up with the possibility that the test was a separate executable, that means that portable shell code still uses double quotes more copiously than the modern shells actually require, and the X-prefix notation was used to ensure that things could not get misinterpreted.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 6
    Most, perhaps all, Unix-like systems still have "test" and "[" executables (on Ubuntu, they're in /usr/bin, not in /bin). This allows them to be used from things other than shell scripts. (Consult your system's documentation; the command is likely to behave subtly differently than the shell built-in -- which itself can vary from one shell to another.) – Keith Thompson Jul 29 '11 at 16:31
12

Ah, this is one of my preferred questions and answers, because I came up with the answer just thinking about it. The X is set just in case the string starts with a -, that can be taken as a flag for the test. Putting an X before just removes that case, and the comparison can still hold.

I also like this because this kind of trick is almost an inheritance from the past, oldest times of computing, and you encounter it when you try to read some of the most portable shell scripts out there (autoconf, configure, etc.)

Diego Sevilla
  • 28,636
  • 4
  • 59
  • 87
  • 1
    makes sense... still, must be historical because `myvar="-somval"; if [ -z $myvar ]; then echo "zero"; fi` doesn't output "zero" (or otherwise puke) on my shell (bash)... – mgalgs Jul 28 '11 at 00:08
  • 2
    @mitch: yes, maybe it will not fail at exactly `-z`, but it is used (out of custom, I may think) just as a marker for all string comparisons. – Diego Sevilla Jul 28 '11 at 00:11
  • 1
    All versions of `test` I tested (bash built-in also) correctly parse commands like `[ -z = -z ]`, `[ ] != [ ]`, etc. –  Nov 14 '12 at 17:11