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
wjandrea
  • 658
RKA
  • 877

3 Answers3

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.

Eliah Kagan
  • 4,155
  • 2
  • 1
    Maybe also mention that this has implication for non-quoted variables that are unset, like this: http://paste.ubuntu.com/25831047/ – Sergiy Kolodyazhnyy Oct 27 '17 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! – Eliah Kagan Oct 27 '17 at 16:01
  • @Eliah Kagan, your answer is more complete and detailed although Ignacio Vazquez-Abrams's response answered my question. – RKA Oct 27 '17 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. – R.. GitHub STOP HELPING ICE Oct 28 '17 at 21:16
  • Your answer also explains test -n. Cheers! –  Sep 07 '18 at 21:52
  • Re: "When there is only one argument between [ and ], that argument is a string that is tested to see if it is empty." Then what's the difference between [ "$x" ] and [ -n "$x" ]? In my case they seem to behave identically; for x="" both return 1, and for x="x" both return 0. – pmor Jan 03 '24 at 12:38
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
  • 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. – Stéphane Chazelas Oct 27 '17 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 ]]). – Stéphane Chazelas Oct 27 '17 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".

glenn jackman
  • 85,964