String comparison with integer in [[ test
One difference between [
and [[
is that [
does not do arithmetic evaluation but [[
does:
$ [ "2 + 2" -eq 4 ] && echo yes
bash: [: 2 + 2: integer expression expected
$ [[ "2 + 2" -eq 4 ]] && echo yes
yes
The second subtlety is that, wherever arithmetic evaluation is performed under bash, empty strings evaluate to 0. For example:
$ x=""; echo $((0 + x))
0
$ [[ "" -eq 0 ]] && echo yes
yes
Documentation
From man bash
:
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
With the -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.
Yes, posix test ([
) would not convert an string to a number on numerical comparisons:
$ sh -c '[ 2+2 -eq 4 ]'
sh: 1: [: Illegal number: 2+2
$ dash -c '[ 2+2 -eq 4 ]'
dash: 1: [: Illegal number: 2+2
$ bash -c '[ 2+2 -eq 4 ] && echo "YES"'
bash: line 0: [: 2+2: integer expression expected
However, not all shells work in the same way:
$ ksh -c '[ 2+2 -eq 4 ] && echo "YES"'
YES
Usual workaround
Make sure that a null or empty value is converted to 0 (works on most shells)
$ dash -c 'a=""; [ "${a:-0}" -gt 3 ] && echo "YES"'
Use arithmetic
Use arithmetic expansion ( may also convert values as 2+2
in some shells (not dash) )
$ dash -c 'a=""; [ "$((a+0))" -gt -3 ] && echo "YES"'
YES
Use [[
The use of the [[
test will convert most strings that would become a number (even if not wanted) in shells that allow [[
:
$ [[ "2+2" -gt 3 ]] && echo "YES"
YES