One difference between
[[ is that
[ does not do arithmetic evaluation but
$ [ "2 + 2" -eq 4 ] && echo yes
bash: [: 2 + 2: integer expression expected
$ [[ "2 + 2" -eq 4 ]] && echo yes
The second subtlety is that, wherever arithmetic evaluation is performed under bash, empty strings evaluate to 0. For example:
$ x=""; echo $((0 + x))
$ [[ "" -eq 0 ]] && echo yes
Shell variables are allowed as operands; parameter expansion is
performed before the expression is evaluated. Within an expression,
shell variables may also be referenced by name without using the
parameter expansion syntax. A shell variable that is null or unset
evaluates to 0 when referenced by name without using the parameter
expansion syntax. The value of a variable is evaluated as an
arithmetic expression when it is referenced, or when a variable which
has been given the integer attribute using declare -i is assigned a
value. A null value evaluates to 0. A shell variable need not have
its integer attribute turned on to be used in an expression. [Emphasis added]
Aside: Security Issues
Note that bash's arithmetic evaluation is a potential security issue. For example, consider:
x='a[$(rm -i *)]'
[[ x -eq 0 ]] && echo yes
-i option, the above is safe but the general lesson is not to use bash's arithmetic evaluation with un-sanitized data.
By contrast, with
[, no arithmetic evaluation is performed and, consequently, the command never attempts to delete files. Instead, it safely generates an error:
$ x='a[$(rm -i *)]'
$ [ "$x" -eq 0 ] && echo yes
bash: [: a[$(rm -i *)]: integer expression expected
For more on this issue, see this answer.