23

My question is on return values produced by this code:

if [ -n ]; then echo "true"; else echo "false"; fi

This prints true.

Its complementary test using [ -z ] also prints true:

if [ -z ]; then echo "true"; else  echo "false"; fi

In the above code, why does the [ -n ] test assume the string value that is not passed at all, as not null?

The code below prints false. This is expected since the passed string value is null and of zero length.

if [ -n "" ]; then echo "true"; else  echo "false"; fi

3 Answers 3

32

[ -n ] does not use the -n test.

The -n in [ -n ] is not a test at all. When there is only one argument between [ and ], that argument is a string that is tested to see if it is empty. Even when that string has a leading -, it is still interpreted as an operand, not a test. Since the string -n is not empty--it contains two characters, - and n, not zero characters--[ -n ] evaluates to true.

As Ignacio Vazquez-Abrams says, where string is a single argument, the test performed on string in [ string ] is the same as the test performed on it by [ -n string ]. When string happens to be -n, nothing special happens. The -n in [ -n ] and the second -n in [ -n -n ] are simply strings being tested for emptiness.

When there is only one argument between [ and ], that argument is always a string to be tested for nonemptiness, even if it happens to be named the same as a test. Similarly, when there are two arguments between [ and ] and the first of them is -n, the second one is always a string to be tested for nonemptiness, even if it happens to be named the same as a test. This is simply because the syntax for [ insists that a single argument between [ and ] or after -n is a string operand.

For the same reason that [ -n ] doesn't use the -n test, [ -z ] doesn't use the -z test.


You can learn more about [ in bash by examining the help for it. Notice that is a shell builtin:

$ type [
[ is a shell builtin

Thus you can run help [ to get help on it:

$ help [
[: [ arg... ]
    Evaluate conditional expression.

    This is a synonym for the "test" builtin, but the last argument must
    be a literal `]', to match the opening `['.

For more information, including what tests are supported and how they work, you will have to see the help on test. When you run the command help test, you'll get a detailed list. Rather than reproduce it all, here's the part about string operators:

      -z STRING      True if string is empty.

      -n STRING
         STRING      True if string is not empty.

      STRING1 = STRING2
                     True if the strings are equal.
      STRING1 != STRING2
                     True if the strings are not equal.
      STRING1 < STRING2
                     True if STRING1 sorts before STRING2 lexicographically.
      STRING1 > STRING2
                     True if STRING1 sorts after STRING2 lexicographically.

Notice that -n STRING and just STRING do the same thing: they test if the string STRING is not empty.

7
  • 2
    Good explanation Commented Oct 27, 2017 at 15:53
  • 1
    Maybe also mention that this has implication for non-quoted variables that are unset, like this: paste.ubuntu.com/25831047 Commented Oct 27, 2017 at 15:55
  • 1
    @SergiyKolodyazhnyy I think it may be better in a separate answer. Variables that are unset, or set but empty, or contain only IFS whitespace, will do that; variables that contain multiple words instead of zero produce another effect and may cause a completely different test to be run. Strings that appear literally with the intent of being operands will only work properly if they contain no spaces or tabs or if they are quoted. The answer would become much longer and more complicated if I covered this topic in an accessible way. If you decide to post answer, feel free to @ me here about it! Commented Oct 27, 2017 at 16:01
  • @Eliah Kagan, your answer is more complete and detailed although Ignacio Vazquez-Abrams's response answered my question.
    – RKA
    Commented Oct 27, 2017 at 18:24
  • 3
    I think you're missing the reason it is and has to be this way: if not, [ "$var" ] would never be usable as a test because$var might expand to -n. Commented Oct 28, 2017 at 21:16
15

[x] is equivalent to [ -nx] even if x starts with - provided there is no operand.

$ [ -o ] ; echo $?
0
$ [ -eq ] ; echo $?
0
$ [ -n -o ] ; echo $?
0
$ [ -n -eq ] ; echo $?
0
2
  • 4
    Note that historically, [ -t ] was testing whether stdout was a terminal (short for [ -t 1 ]) and some shells are still doing it (in the case of ksh93 only when that -t is literal), so it's better to use [ -n "$var" ] than [ "$var" ]. Though that would still fail in some old test implementations for values of $var like =, in which case [ "" != "$var" ] or [ "x$var" != x ] or case $x in "")... may be better. Commented Oct 27, 2017 at 15:58
  • 3
    Interesting choice of -o here. In some shells like bash, -o is both a unary (test if an option is set) and binary (or) operator, so things like [ ! -o monitor ] are ambiguous. Is it testing that the monitor option is not set, or that either ! or monitor are non-empty strings? POSIX rules decide: the latter (that's different with [[ ! -o monitor ]]). Commented Oct 27, 2017 at 16:04
14

[ -n ] is true because the [ command (aka the test command) acts upon the number of arguments it is given. If it is given only a single argument, the result is "true" if the argument is a non-empty string. "-n" is a string with 2 characters, not empty, therefore "true".

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .