Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between `test -n $a` and `test -n "$a"`

Tags:

bash

shell

$ a=""
$ test -n $a
$ echo $?
0
$ test -n a
$ echo $?
0
$ test -n "$a"
$ echo $?
1

What is the difference between them? Why are the results different?

like image 224
yang.zhou Avatar asked Mar 05 '26 00:03

yang.zhou


2 Answers

When $a is empty, test -n $a expands to this:

'test' '-n'

When test only receives one argument like this, it does the default test, which is probably the source of your confusion. The default test is the same as -n, i.e. the following two are equivalent:

test string
test -n string

So your test is whether the string -n has a length greater than zero. It does, so you get a 0 exit status.

Your next example:

'test' '-n' 'a'

tests whether the length of the literal string a is greater than zero. It is, so you get a 0 exit status again.

The final test is probably what you wanted all along. It expands to this:

'test' '-n' ''

Now two arguments are passed, but the second one is empty, so the -n test fails and you get a 1 exit status.

Conclusion

Always quote your variables!


You can view how the shell expands your statement using set -x:

$ set -x
$ test -n $a
+ test -n
$ test -n "$a"
+ test -n ''
like image 55
Tom Fenech Avatar answered Mar 06 '26 17:03

Tom Fenech


Let's play a game called "Count the arguments". How many arguments does test receive in the call

test -n $a

If you said "2" (meaning -n and $a), sorry, that's incorrect. The correct answer is "We don't know".

We don't know because we don't know what the value of the parameter a is. If a=foo, then test receives two arguments, -n and foo. If a="foo bar", then test gets three arguments after word-splitting takes place, -n, foo, and bar.

What if a=*? The if you said "2, -n and *", you get points for trying, but you are still wrong. That's because the result of expanding $a, if it contains certain characters like *, undergoes not just word-splitting, but pathname expansion as well. There is no way to tell how many arguments test will get without knowing how many files are in the current working directory, because * will expand to a sequence of file names, one word per file.

By contrast, test -n "$a" always passes two arguments to test. The quoted parameter expansion is not subject to word-splitting or pathname expansion, so whatever value a has (foo, foo bar, or *), that exact string is always passed to test as a single argument.

like image 21
chepner Avatar answered Mar 06 '26 16:03

chepner