>
== What is the difference between test, [ and [[ ? ==
The open square bracket `[` command (aka `test` command) and the `[[ ... ]]` test construct are used to evaluate expressions. `[[ ... ]]` works only in the Korn shell (where it originates), Bash, Zsh, and recent versions of Yash and busybox `sh` (if enabled at compilation time, and still very limited there especially in the `hush`-based variant), and is more powerful; `[` and `test` are POSIX utilities (generally builtin). POSIX doesn't specify the `[[ ... ]]` construct (which has a specific syntax with significant variations between implementations) though allows shells to treat `[[` as a keyword. Here are some examples:
{{{
#POSIX
[ "$variable" ] || echo 'variable is unset or empty!' >&2
[ -f "$filename" ] || printf 'File does not exist or is not a regular file: %s\n' "$filename" >&2
}}}
{{{
if [[ ! -e $file ]]; then
echo "File doesn't exist or is in an inaccessible directory or is a symlink to a file that doesn't exist." >&2
fi
if [[ $file0 -nt $file1 ]]; then
printf '%s\n' "file $file0 is newer than $file1 (or $file1 is no accessible)"
# (behaviour varies between shells if $file1 is not accessible)
fi
}}}
To cut a long story short: {{{test}}} implements the old, portable syntax of the command. In almost all shells (the oldest Bourne shells are the exception), {{{[}}} is a synonym for {{{test}}} (but requires a final argument of {{{]}}}). Although all modern shells have built-in implementations of {{{[}}}, there usually still is an external executable of that name, e.g. {{{/bin/[}}}. [[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html|POSIX]] defines a mandatory feature set for `[`, but almost every shell offers extensions to it. So, if you want portable code, you should be careful not to use any of those extensions.
{{{[[}}} is a new, improved version of it, and it is a keyword rather than a program. This makes it easier to use, as shown below.
Although {{{[}}} and {{{[[}}} have much in common and share many expression operators like "{{{-f}}}", "{{{-s}}}", "{{{-n}}}", and "{{{-z}}}", there are some notable differences. Here is a comparison list:
||'''Feature''' ||'''new test''' {{{[[}}} ||'''old test''' {{{[}}} ||'''Example''' ||
||<|4>string comparison ||{{{>}}} ||{{{\>}}} [[#np|(*)]] ||{{{[[ a > b ]] || echo "a does not come after b"}}} ||
||{{{<}}} ||{{{\<}}} [[#np|(*)]] ||{{{[[ az < za ]] && echo "az comes before za"}}} ||
||{{{=}}} (or {{{==}}}) ||{{{=}}} ||{{{[[ a = a ]] && echo "a equals a"}}} ||
||{{{!=}}} ||{{{!=}}} ||{{{[[ a != b ]] && echo "a is not equal to b"}}} ||
||<|6>integer comparison ||{{{-gt}}} ||{{{-gt}}} ||{{{[[ 5 -gt 10 ]] || echo "5 is not bigger than 10"}}} ||
||{{{-lt}}} ||{{{-lt}}} ||{{{[[ 8 -lt 9 ]] && echo "8 is less than 9"}}} ||
||{{{-ge}}} ||{{{-ge}}} ||{{{[[ 3 -ge 3 ]] && echo "3 is greater than or equal to 3"}}} ||
||{{{-le}}} ||{{{-le}}} ||{{{[[ 3 -le 8 ]] && echo "3 is less than or equal to 8"}}} ||
||{{{-eq}}} ||{{{-eq}}} ||{{{[[ 5 -eq 05 ]] && echo "5 equals 05"}}} ||
||{{{-ne}}} ||{{{-ne}}} ||{{{[[ 6 -ne 20 ]] && echo "6 is not equal to 20"}}} ||
||<|2>conditional evaluation ||{{{&&}}} ||{{{-a}}} [[#np2|(**)]] ||{{{[[ -n $var && -f $var ]] && echo "$var is a file"}}} ||
||{{{||}}} ||{{{-o}}} [[#np2|(**)]] ||{{{[[ -b $var || -c $var ]] && echo "$var is a device"}}} ||
||expression grouping ||{{{(...)}}} ||{{{\( ... \)}}} [[#np2|(**)]] ||{{{[[ $var = img* && ($var = *.png || $var = *.jpg) ]] &&}}}<
>{{{echo "$var starts with img and ends with .jpg or .png"}}} ||
||Pattern matching ||{{{=}}} (or {{{==}}}) ||(not available) ||{{{[[ $name = a* ]] || echo "name does not start with an 'a': $name"}}} ||
||RegularExpression matching ||{{{=~}}} ||(not available) ||{{{[[ $(date) =~ ^Fri\ ...\ 13 ]] && echo "It's Friday the 13th!"}}} ||
<> (*) This is an extension to the POSIX standard; some shells may have it, others may not.
<> (**) The `-a` and `-o` operators, and `( ... )` grouping, are defined by POSIX but only for strictly limited cases, and are marked as deprecated. Use of these operators is discouraged; you should use multiple `[` commands instead:
* `if [ "$a" = a ] && [ "$b" = b ]; then ...`
* `if [ "$a" = a ] || { [ "$b" = b ] && [ "$c" = c ];}; then ...`
Special primitives that {{{[[}}} is defined to have, but {{{[}}} may be lacking (depending on the implementation):
||'''Description''' ||'''Primitive''' ||'''Example''' ||
||entry (file or directory) exists ||{{{-e}}} ||{{{[[ -e $config ]] && echo "config file exists: $config"}}} ||
||file is newer/older than other file ||{{{-nt}}} / {{{-ot}}} ||{{{[[ $file0 -nt $file1 ]] && echo "$file0 is newer than $file1"}}} ||
||two files are the same ||{{{-ef}}} ||{{{[[ $input -ef $output ]] && { echo "will not overwrite input file: $input"; exit 1; } }}} ||
||negation ||{{{!}}} ||{{{[[ ! -u $file ]] && echo "$file is not a setuid file"}}} ||
But there are more subtle differences.
* No WordSplitting or [[glob]] expansion will be done for {{{[[}}} (and therefore many arguments need not be quoted):
{{{
file="file name"
[[ -f $file ]] && echo "$file is a regular file"
}}}
will work even though {{{$file}}} is not quoted and contains whitespace. With {{{[}}} the variable needs to be quoted:
{{{
file="file name"
[ -f "$file" ] && echo "$file is a regular file"
}}}
This makes {{{[[}}} easier to use and less error-prone.
* Parentheses in {{{[[}}} do not need to be escaped:
{{{
[[ -f $file1 && ( -d $dir1 || -d $dir2 ) ]]
[ -f "$file1" -a \( -d "$dir1" -o -d "$dir2" \) ]
}}}
* As of bash 4.1, string comparisons using `<` or `>` respect the current [[locale]] when done in `[[`, but '''not''' in `[` or `test`. In fact, `[` and `test` have ''never'' used locale collating order even though past man pages ''said'' they did. Bash versions prior to 4.1 do not use locale collating order for `[[` either.
As a rule of thumb, {{{[[}}} is used for strings and files. If you want to compare numbers, use an ArithmeticExpression, e.g.
{{{
# Bash
i=0
while (( i < 10 )); do ...
}}}
When should the new test command {{{[[}}} be used, and when the old one {{{[}}}? If portability/conformance to POSIX or the BourneShell is a concern, the old syntax should be used. If on the other hand the script requires [[BASH]], Zsh, or KornShell, the new syntax is usually more flexible, but not necessarily backwards compatible.
For reasons explained in the theory section below, any problem with an operator used with `[[` is an unhandleable parse-time error that will cause bash to terminate, even if the command is never evaluated.
{{{
# Example of improper [[ usage.
# Notice that isSet is never even called.
$ bash-3.2 <<\EOF
if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 2))); then
isSet() { [[ -v $1 ]]; }
else
isSet() { [[ ${1+_} ]]; }
fi
EOF
bash-3.2: line 2: conditional binary operator expected
bash-3.2: line 2: syntax error near `$1'
bash-3.2: line 2: ` isSet() { [[ -v $1 ]]; }'
}}}
If backwards-compatibility were desired then `[ -v` should have been used instead. The only other alternatives would be to use an alias to conditionally expand during the function definition, or `eval` to defer parsing until the command is actually reached at runtime.
See the [[BashGuide/TestsAndConditionals|Tests and Conditionals]] chapter in the BashGuide.
=== Theory ===
The theory behind all of this is that `[` is a simple command, whereas `[[` is a compound command. `[` receives its arguments as any other command would, but most compound commands introduce a special parsing context which is performed before any other processing. Typically this step looks for special `reserved words` or `control operators` specific to each compound command which split it into parts or affect control-flow. The Bash test expression's logical and/or operators can short-circuit because they are special in this way (as are e.g. `;;`, `elif`, and `else`). Contrast with ArithmeticExpression, where all expansions are performed left-to-right in the usual way, with the resulting string being subject to interpretation as arithmetic.
* The arithmetic compound command has no special operators. It has only one evaluation context - a single arithmetic expression. Arithmetic expressions have operators too, some of which affect control flow during the arithmetic evaluation step (which happens last).
{{{
# Bash
(( 1 + 1 == 2 ? 1 : $(echo "This doesn't do what you think..." >&2; echo 1) ))
}}}
* Test expressions on the other hand do have "operators" as part of their syntax, which lie on the other end of the spectrum (evaluated first).
{{{
# Bash
[[ '1 + 1' -eq 2 && $(echo "...but this probably does what you expect." >&2) ]]
}}}
* Old-style tests have no way of controlling evaluation because its arguments aren't special.
{{{
# Bash
[ $((1 + 1)) -eq 2 -o $(echo 'No short-circuit' >&2) ]
}}}
* Different error handling is made possible by searching for special compound command tokens before performing expansions. `[[` can detect the presence of expansions that don't result in a word yet still throw an error if none are specified. Ordinary commands can't.
{{{
# Bash
( set -- $(echo 'Unquoted null expansions do not result in "null" parameters.' >&2); echo $# )
[[ -z $(:) ]] && echo "-z was supplied an arg and evaluated empty."
[ -z ] && echo "-z wasn't supplied an arg, and no errors are reported. There's no possible way Bash could enforce specifying an argument here."
[[ -z ]] # This will cause an error that ordinary commands can't detect.
}}}
* For the very same reason, because `[`'s operators are just "arguments", unlike `[[`, you ''can'' specify operators as parameters to an ordinary `test` command. This might be seen as a limitation of `[[`, but the downsides outweigh the good almost always.
{{{
# ksh93
args=(0 -gt 1)
(( $(print '0 > 1') )) # Valid command, Exit status is 1 as expected.
[ "${args[@]}" ] # Also exit 1.
[[ ${args[@]} ]] # Valid command, but is misleading. Exit status 0. set -x reveals the resulting command is [[ -n '0 -gt 1' ]]
}}}
* Do keep in mind which operators belong to which shell constructs. Order of expansions can cause surprising results especially when mixing and nesting different evaluation contexts!
{{{
# ksh93
typeset -i x=0
( print "$(( ++x, ${ x+=1; print $x >&2;}1, x ))" ) # Prints 1, 2
( print "$(( $((++x)), ${ x+=1; print $x >&2;}1, x ))" ) # Prints 2, 2 - because expansions are performed first.
}}}
----
CategoryShell