When converting this confusing observable behavior into a test script and a question that may be appropriate for StackOVerflow, several events occurred that led to the answer to this “why” question.
The response TL, DR is given in section 5 below; I describe the whole process of discovery because I (re) learned a lot of things along this path, and I think that others will follow the same, if not very similar, path of discovery. Google is not always your friend!
1. complete failure of google fu
Firstly, "google" did not deliver anything when searching for bash test $@ odd result or similar queries, since it removes all important $@ . Rephrasing $@ as dollar at did not spit out anything useful, which caused me concern. I even started to suspect that I was running bash (mSysGit bash on Windows).
Thanks to this debacle, I went and looked at how there could be an official name (jargon) for this $@ (maybe 2 decades ago that I last read the bash manual, I’ve ever worked on it since this is the moment when I get punished for my laziness without learning the language in which I write (small bits) of code.)
2. Name: "positional parameters". $@ (and $* )
Another search round led to http://www.tldp.org/LDP/abs/abs-guide.pdf , which told me that $@ (and $* - oh, I forgot all about you!) are "positional parameters."
3. What is the exact behavior of test ?
During this search, I also found http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules , which explains how test behaves when the number of arguments may not be what you would expect.
But then I had quotes around this $@ , right? So this should be the only argument to test , then even if the argument list is empty !? ( $# = 0 )
(Wrong! This is not!)
test expression evaluation rule set
Copying (with minimal editing in accordance with this question) is a very important section of the link above - this is what made my brain click, guess, and then understand:
http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules :
The number of argument rules
The built-in test , especially hidden under its name [ , may seem simple, but it actually causes a lot of trouble. One of the difficulties is that the behavior of test depends not only on its arguments, but also on the number of its arguments .
Here are the rules taken from the manual (Note: this is for the test command, for [ number of arguments is calculated without the final ] , for example [ ] follows the "null arguments" "):
0 arguments
The expression is false .
1 argument
The expression is true if and only if the argument is non-zero.
2 arguments
If the first argument ! (exclamation point), the expression is true if and only if the second argument is null.
If the first argument is one of the unary operators listed above in the syntax rules (for example, -n and -z ), the expression true if the unary test is true .
If the first argument is not a valid unary conditional statement, the expression false .
3 arguments
If the second argument is one of the binary conditional statements listed above according to the syntax rules, the result of the expression is the result of a binary test using the first and third arguments as operands.
If the first argument ! , this is the negation of a test with two arguments using the second and third arguments.
If the first argument is exactly equal ( and the third argument is exactly equal ) , the result is a one-parameter test of the second argument. Otherwise, the expression is false . The -a and -o operators are considered binary operators in this case ( Note : this means that the -a operator is not a file operator in this case!)
4 arguments
If the first argument ! , the result is a negation of the expression with three arguments, consisting of the remaining arguments. Otherwise, the expression is parsed and evaluated according to priority using the rules listed above.
5 or more arguments
The expression is parsed and evaluated according to priority using the rules listed above.
These rules may seem complicated, but in practice it is not so bad. Knowing them can help you explain some of the “inexplicable” behaviors you may encounter:
(note: in the next section, paraphrasing the original corresponds to this question!)
function test { if [ -n " $@ " ] ; then echo "argument list is not empty"; fi } test
This code prints "the argument list is not empty," although -n something must be false if something is an empty string "" - why?
Here " $@ " expands to a list of empty arguments , i.e. " $@ " leads to the actual nothing (Bash removes it from the command argument list!), so the test is actually [ -n ] and falls into the "one argument" rule, the only argument is " -n ", which is not null, and therefore the test returns true . Therefore, the usual solution that a parameter extension should indicate, for example. [ -n "$var" ] , so the test always has 2 arguments, even if the second line is an empty string, it does not work for " $@ " .
These rules also explain why, for example, -a and -o can have multiple meanings.
4. Another test run ...
The description at http://www.tldp.org/LDP/abs/abs-guide.pdf and the non-obvious bits of the test behavior, as described at http://wiki.bash-hackers.org/commands/classictest#number_of_arguments_rules made me run fourth test with script test specified in question:
$ ./bash_weirdness.sh xyz -> args: 'xyz' --- empty --- 1.A2.EMPTY - empty? 1.B1.NOT.TEST - empty? 1.B3.NOT.NE - empty? --- space --- 2.A1.TEST - not empty? 2.A3.NE - not empty? 2.B2.NOT.EMPTY - not empty? --- $@ --- util/bash_weirdness.sh: line 25: test: y: binary operator expected util/bash_weirdness.sh: line 26: test: too many arguments util/bash_weirdness.sh: line 27: test: too many arguments util/bash_weirdness.sh: line 28: test: y: binary operator expected 3.B1.NOT.TEST - empty? util/bash_weirdness.sh: line 29: test: too many arguments 3.B2.NOT.EMPTY - not empty? util/bash_weirdness.sh: line 30: test: too many arguments 3.B3.NOT.NE - empty? --- $* --- 4.A1.TEST - not empty? 4.A3.NE - not empty? 4.B2.NOT.EMPTY - not empty?
Now there is a clue!
5. Answering the question "why?" question
Now I know.
$@ , even when quoted as " $@ " , somehow "translates to the exact number of arguments available in $@ , which for an empty argument list ( $# = 0 ) means that " $@ " matches exactly nothing: test -n " $@ " , so it" extends "to test -n , which, following the rules described in http://wiki.bash-hackers.org/commands/classictest# number_of_arguments_rules , is almost identical to the example described here and corresponds to the same rule 1 of the argument: -n in test -n is not a test option: return true if string is non-empty , but rather "one argument", not zero and , Consequently, test -n " $@ " --> test -n --> test "-n" --> TRUE , which creates an unexpected error test result, shown in the third test run in the question.
“Somehow” is related to what bash must do internally to ensure that the number of parameters in " $@ " always equal to the number of rounds in loop statements, such as for f in " $@ " ; do ... done for f in " $@ " ; do ... done and always matches the number of $# parameters.
If processing " $@ " for an empty list of parameters will lead to the creation of an empty string "" , and not anything at all, then such loop operators will perform one (1) round instead of the expected rounds of zero (0) which would be extremely contradictory.
Once you realize this, the description in http://www.tldp.org/LDP/abs/abs-guide.pdf for $@ compared to $* obvious.
6. Deja vu. Now that I know, SO suddenly provides comparable questions (and answers)
This felt exactly the same as in the old days when I read the UNIX manual pages after working with VMS: these “help pages” were very brief and more time than not only useful to me, as soon as I already gained knowledge through other channels.
[Edit] Retrospection Note:
I say “would not help me” for many of the SO questions listed below, just because at the time I was writing this answer I was thinking and wondering if my question was really a duplicate question. These other questions and answers presented there are all very valuable in themselves.
The snippet “didn’t help me,” so you can understand that my brain needs something else to resolve my confusion and loss of confidence in my cars. He says: “I need this, not one of them,” and thus shows my internal argument whether this question is duplicated or not.
My own conclusion now: one question is technically almost the same (except ! In ! test -z ), while its answers are very close to what I need, but at least for me this is not a duplicate answer, since it pays much more attention to the depth of detail of why the test and $@ interactions. The exact question needed to make this answer appropriate makes this, at least for me, a retrospective rather than a duplicate question. Although he is close to being alone.
Here they are: