In bash, why does the if [ -n ] test return true for an empty string? [duplicate]

Any idea why echo statement is being executed in both scenarios?

Outputs it worked:

#!/bin/bash

rizwan=''
if [ -n $rizwan ]; then
  echo "it worked"
fi

Replace -n with -z, and it outputs it worked when expecting no output:

#!/bin/bash

rizwan=''
if [ -z $rizwan ]; then
  echo "it worked"
fi

You may know it or not, but let's make it clear: [ is not a part of the if syntax. I mean if [ … behaves like if true, if false or if any_command, where [, true, false and any_command are commands. They return exit status and this is what matters to if.

Even if [ is a builtin (and it is in Bash), it behaves like a regular command. There's even a standalone [ executable (e.g. /usr/bin/[) because it's required by POSIX.

This means [ -n whatever ] is just a [ command with few arguments. Yes, ] is just an argument, it's not a delimiter or anything like that. The command named [ just requires ] to be its last argument. This way the code is more legible, ] serves no other purpose.

The command test is equivalent to [. The only difference is test does not require ].
[ -n whatever ] is equivalent to test -n whatever.

If the shell expands $rizwan to an empty string, the snippets [ -n $rizwan ] and [ -z $rizwan ] become [ -n ] and [ -z ] respectively. The [ command is not aware there was something between -n (or -z) and ] in your code, it gets exactly two arguments. Both tests are treated as the [ STRING ] syntax where STRING is either -n or -z. This syntax tests if STRING is not empty. Your tests always succeed because neither -n nor -z is an empty string.

To make the snippets work as [ -n STRING ] or [ -z STRING ], you need to double-quote the variable. The shell, after expanding [ -z "$rizwan" ], will pass exactly three arguments to [. They will be -z, the expanded value of the variable (even if it's an empty string) and ]. The tool will get all the arguments you wanted it to get.

If unquoted $rizwan in your examples expanded to more than one word, [ would get more than three arguments. In general the final result would depend on the variable content and the implementation of [.

Compare Why does my shell script choke on whitespace or other special characters?.

Note [[ in Bash is different. It's a keyword, it's integrated with the shell parser, it changes some rules, it's actually aware of variables and quotes between [[ and ]]. If you used [[ instead of [ (and consequently ]] instead of ]), then your code would work as you expected. See this answer.

[ is the portable one.


As variable rizwan is empty, your if command becomes:

if [ -n  ]

The syntax [ STRING ] tests if the string is empty (no characters). So in your case of [ -n ], the string -n is evidently not-empty.