The difference between [[ … ]]
and [ … ]
is mostly covered in Why does parameter expansion with spaces without quotes work inside double brackets "[[" but not inside single brackets "["?.
Crucially, [[ … ]]
is special syntax, whereas [
is a funny-looking name for a command. [[ … ]]
has special syntax rules for what's inside, [ … ]
doesn't.
With the added wrinkle of a wildcard, here's how [[ $a == z* ]]
is evaluated:
- Parse the command: this is the
[[ … ]]
conditional construct around the conditional expression $a == z*
.
- Parse the conditional expression: this is the
==
binary operator, with the operands $a
and z*
.
- Expand the first operand into the value of the variable
a
.
- Evaluate the
==
operator: test if the value of the variable a
matches the pattern z*
.
- Evaluate the conditional expression: its result is the result of the conditional operator.
- The command is now evaluated, its status is 0 if the conditional expression was true and 1 if it was false.
Here's how [ $a == z* ]
is evaluated:
- Parse the command: this is the
[
command with the arguments formed by evaluating the words $a
, ==
, z*
, ]
.
- Expand
$a
into the value of the variable a
.
- Perform word splitting and filename generation on the parameters of the command.
- For example, if the value of
a
is the 6-character string foo b*
(obtained by e.g. a='foo b*'
) and the list of files in the current directory is (bar
, baz
, qux
, zim
, zum
), then the result of the expansion is the following list of words: [
, foo
, bar
, baz
, ==
, zim
, zum
, ]
.
- Run the command
[
with the parameters obtained in the previous step.
- With the example values above, the
[
command complains of a syntax error and returns the status 2.
Note: In [[ $a == z* ]]
, at step 3, the value of a
does not undergo word splitting and filename generation, because it's in a context where a single word is expected (the left-hand argument of the conditional operator ==
). In most cases, if a single word makes sense at that position then variable expansion behaves like it does in double quotes. However, there's an exception to that rule: in [[ abc == $a ]]
, if the value of a
contains wildcards, then abc
is matched against the wildcard pattern. For example, if the value of a
is a*
then [[ abc == $a ]]
is true (because the wildcard *
coming from the unquoted expansion of $a
matches bc
) whereas [[ abc == "$a" ]]
is false (because the ordinary character *
coming from the quoted expansion of $a
does not match bc
). Inside [[ … ]]
, double quotes do not make a difference, except on the right-hand side of the string matching operators (=
, ==
, !=
and =~
).
It's very simple. You just have to understand []
, [[]]
and (())
as completely different beasts.
[ expression ]
is just an invocation of a program [
(just a different name for test
) and takes the expression as arguments, just like for all command calls in bash. That means, you must use whitespace between arguments, especially after [
and before ]
, and keywords and special characters have to be escaped or quoted, and variables are expanded in the usual way.
Within the expression, -gt, -lt
and related are used for numerical comparison, while >, <
and the sort, are for string comparisons. Think of [
as nothing to do with bash, just calling a command that evaluates expressions and returns 0 (true) or non-zero (false), as all programs do.
Boolean operators &&
and ||
can't be used in there, because they get interpreted by bash and not passed to test
as arguments. -a
and -o
are used instead. But you can always do [ test1 ] && [ test2 ] || [ test3 ]
, which are 3 test invocations, combined by bash
's usual boolean operators (that is, if first fails, the second is skipped).
Note that bash
still comes with its own implementation of [
(builtin), but that doesn't change the way it's treated syntactically.
[[ ]]
and (( ))
are not builtin replacements for commands, but parts of a special syntax, so different rules apply. See man page for details on how variable names are wildcards are treated there. Boolean operators &&
and ||
have the usual meaning.
The [[ ]]
one is string-centered: all comparsions are for strings (but are more "modern", locale-aware than in [
, although test
could be platform-dependent). It handles also file tests, regex and so on.
The (( ))
is for arithmetic expressions. Variable names don't require $
there, and you can mostly just write mathematical expressions in there - that of course includes comparisons (which are numerical). Comparisons in this case are no different from any other arithmetic expression, true is 1, false is 0, and you can write stuff like (( x=y*(z>4) ))
. If used in a conditional expression, nonzero is true and zero is false. You can also capture the result as $(( ))
.
So:
[[ ]]
string conditionals, pattern matching and file tests
(( ))
arithmetic expressions and conditionals
[ ]
test command: handles both, special nonstandard syntax for boolean operators and comparison operators
Best Answer
In
bash
, with context of two argumentstest
command,-a file
and-e file
are the same. But they have some difference, because-a
is also a binary operator.-e
unary is defined by POSIX, but-a
unary isn't. POSIX only defines-a
binary (See test POSIX).POSIX defines three arguments
test
behaviour:So
-a
also leads to strange result:-a
is considered as binary operator in context of three arguments. See Bash FAQ question E1. POSIX also mentions that-a
is get from KornShell but was changed later to-e
because it makes confusing between-a
binary and-a
unary.-a
binary is also marked as obsolescent, because it leads to some ambiguous expression, which has greater than 4 arguments. With these >4 arguments expression, POSIX defines the result is unspecified.