Originally Posted by grail
Well interestingly if you use [] you get perhaps more informative error messages:
Yes, I tried that. I am not sure if the parsing mechanism is the same as with '['. But the behavior of '[[' differs:
$ [[ 1 -a 3 -a 4 ]] && echo true || echo false
bash: conditional binary operator expected
bash: syntax error near `-a'
$ [ 1 -a 3 -a 4 ] && echo true || echo false
So I'd like to focus on '[' before tackling '[['.
I found the section in bash man-page that describes the intended behavior of '['"
[ expr ]
Return a status of 0 or 1 depending on the evaluation of the conditional expres‐
sion expr. Each operator and operand must be a separate argument. Expressions
are composed of the primaries described above under CONDITIONAL EXPRESSIONS. test
does not accept any options, nor does it accept and ignore an argument of -- as
signifying the end of options.
Expressions may be combined using the following operators, listed in decreasing
order of precedence. The evaluation depends on the number of arguments; see
! expr True if expr is false.
( expr )
Returns the value of expr. This may be used to override the normal prece‐
dence of operators.
expr1 -a expr2
True if both expr1 and expr2 are true.
expr1 -o expr2
True if either expr1 or expr2 is true.
test and [ evaluate conditional expressions using a set of rules based on the num‐
ber of arguments.
The expression is false.
The expression is true if and only if the argument is not null.
If the first argument is !, the expression is true if and only if the sec‐
ond argument is null. If the first argument is one of the unary condi‐
tional operators listed above under CONDITIONAL EXPRESSIONS, the expression
is true if the unary test is true. If the first argument is not a valid
unary conditional operator, the expression is false.
If the second argument is one of the binary conditional operators listed
above under CONDITIONAL EXPRESSIONS, the result of the expression is the
result of the binary test using the first and third arguments as operands.
The -a and -o operators are considered binary operators when there are
three arguments. If the first argument is !, the value is the negation of
the two-argument test using the second and third arguments. If the first
argument is exactly ( and the third argument is exactly ), the result is
the one-argument test of the second argument. Otherwise, the expression is
If the first argument is !, the result is the negation of the three-argu‐
ment expression composed of the remaining arguments. Otherwise, the
expression is parsed and evaluated according to precedence using the rules
5 or more arguments
The expression is parsed and evaluated according to precedence using the
rules listed above.
Now let's have a look at the intended behavior when there are three
$ [ 1 -a -1 ] && echo true || echo false
'[' sees three arguments and that the second argument is the binary operator '-a'. According to the man-page it determines that the first argument and the second argument must
be operands and thus treated as such.
To further verify that the second argument is decisive for its behavior when there are three
arguments passed, consider the following:
# According to the man-page '-a' can also be a unary operator:
# -a file
# True if file exists.
$ [ ! -a file ] && echo true || echo false
The fact that there are three
arguments determines that '-a' has to be the binary operator. Hence, '!' is not treated as the negation operator but simply as string. So we can not use this construct to test if a file is present and then negate the outcome. To do this we have to change the order in which the expression is being evaluated. This can be done with braces:
$ [ ! \( -a file \) ] && echo true || echo false
According to the five-arguments-rule
the braces cause to evaluate the expression inside them first which results in '-a' being interpreted according to the two-arguments-rule
as unary operator.
More fun with unary operators:
$ [ 1 -a -a file ] && echo true || echo false
$ [ 1 -a -a nofile ] && echo true || echo false
Unary operator '-a' takes precedence over binary '-a' if there are more than three
arguments. According to the four-argument-rule
the above first evaluates '-a file' according to the two-rule-argument
and the result according to the three-rule-argument
According to the five-or-more-rule
the following should have worked:
$ [ \( 1 -a -1 \) -a 4 ] && echo true
bash: [: -1: unary operator expected
$ [ 1 -a \( -1 -a 4 \) ] && echo true
bash: [: -1: unary operator expected
At least with braces the expr. within should have been evaluated according to the three-argument-rule
. Therefore it should have determined that '-a' is a binary operator and treat '-1' as operand. Enclosed within braces, there should be no confusion about misinterpreting '-1' as unary operator.
Apparently, this is not the case.
After all, I am more and more inclined to consider this behavior as a bug. Or is my logic failing me?