4675
Comment: Separated string and integer comparisons (e.g. [[ 10 -gt 5 ]] but [[ 10 < 5 ]], and [[ 5 -eq 05 ]] but [[ 5 != 05 ]])
|
7247
Theory for geeks.
|
Deletions are marked like this. | Additions are marked like this. |
Line 3: | Line 3: |
{{{[}}} ("test" command) and {{{[[}}} ("new test" command) are used to evaluate expressions. Some examples: | `[` ("test" command) and `[[` ("new test" command) are used to evaluate expressions. `[[` works only in Bash and Korn shell, and is more powerful; `[` and `test` are available in POSIX shells. Here are some examples: |
Line 31: | Line 31: |
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/[}}}. | 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. |
Line 33: | Line 33: |
{{{[[}}} is a new improved version of it, which is a keyword, not a program. This has beneficial effects on the ease of use, as shown below. {{{[[}}} is understood by KornShell and [[BASH]] (e.g. 2.03), but not by the older POSIX or BourneShell. | {{{[[}}} is a new improved version of it, and is a keyword, not a program. This makes it easier to use, as shown below. {{{[[}}} is understood by KornShell and [[BASH]] (e.g. 2.03), but not by the older POSIX or BourneShell. |
Line 35: | Line 35: |
Although {{{[}}} and {{{[[}}} have much in common, and share many expression operators like "-f", "-s", "-n", "-z", there are some notable differences. Here is a comparison list: | Although {{{[}}} and {{{[[}}} have much in common, and share many expression operators like "-f", "-s", "-n", "-z", there are some notable differences. Here is a comparison list: |
Line 38: | Line 38: |
||<rowspan=4>string comparison||>||\>||{{{[[ a > b ]] || echo "a does not come before b"}}}|| ||<||\<||{{{[[ az < za ]] && echo "az comes before za"}}}|| |
||<rowspan=4>string comparison||>||\> [[#np|(*)]]||{{{[[ a > b ]] || echo "a does not come before b"}}}|| ||<||\< [[#np|(*)]]||{{{[[ az < za ]] && echo "az comes before za"}}}|| |
Line 48: | Line 48: |
||<rowspan=2>expression grouping||&&||-a||{{{[[ -n $var && -f $var ]] && echo "$var is a file"}}}|| ||{{{||}}}||-o||-|| |
||<rowspan=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) ]] &&}}}<<BR>>{{{echo "$var starts with img and ends with .jpg or .png"}}}|| |
Line 52: | Line 53: |
<<Anchor(np)>> (*) This is an extension to the POSIX standard; some shells may have it, and some may not. <<Anchor(np2)>> (**) The `-a` and `-o` operators, and `( ... )` grouping, are defined in POSIX but only for strictly limited cases. 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 ...` |
|
Line 59: | Line 68: |
||negation||!||-|| | ||negation||!||{{{[[ ! -u $file ]] && echo "$file is not a setuid file"}}}|| |
Line 93: | Line 102: |
=== 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 specific to the compound command which is performed before any ordinary Bash expansions or commands which might be contained. This is for example what allows the Bash test expression's logical and/or operators to be short-circuiting, or the `case..esac` expression to handle its `;;` operator specially. 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. {{{ # Bash (( 0 ? $(echo "This doesn't do what you think..." >&2; echo 1) : 1 )) [[ '1 + 1' -eq 2 && $(echo "...but this probably does what you expect." >&2) ]] }}} {{{ # 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. (( $(print 'x < 1') )) # Valid command, Exit status is 0 [[ $(print 'x -lt 1') ]] # Valid command, But doesn't do what you think. set -x reveals the resulting command is [[ -n 'x -lt 1' ]] }}} |
What is the difference between test, [ and [[ ?
[ ("test" command) and [[ ("new test" command) are used to evaluate expressions. [[ works only in Bash and Korn shell, and is more powerful; [ and test are available in POSIX shells. Here are some examples:
if [ -z "$variable" ] then echo "variable is empty!" fi if [ ! -f "$filename" ] then echo "not a valid, existing file name: $filename" fi
and
if [[ ! -e $file ]] then echo "directory entry does not exist: $file" fi if [[ $file0 -nt $file1 ]] then echo "file $file0 is newer than $file1" 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/[. 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 is a keyword, not a program. This makes it easier to use, as shown below. [[ is understood by KornShell and BASH (e.g. 2.03), but not by the older POSIX or BourneShell.
Although [ and [[ have much in common, and share many expression operators like "-f", "-s", "-n", "-z", there are some notable differences. Here is a comparison list:
Feature |
new test [[ |
old test [ |
Example |
string comparison |
> |
\> (*) |
[[ a > b ]] || echo "a does not come before b" |
< |
\< (*) |
[[ az < za ]] && echo "az comes before za" |
|
= (or ==) |
= |
[[ a == a ]] && echo "a equals a" |
|
!= |
!= |
[[ a != b ]] && echo "a is not equal to b" |
|
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" |
|
conditional evaluation |
&& |
-a (**) |
[[ -n $var && -f $var ]] && echo "$var is a file" |
|| |
-o (**) |
[[ -b $var || -c $var ]] && echo "$var is a device" |
|
expression grouping |
(...) |
\( ... \) (**) |
[[ $var = img* && ($var = *.png || $var = *.jpg) ]] && |
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, and some may not.
(**) The -a and -o operators, and ( ... ) grouping, are defined in POSIX but only for strictly limited cases. 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 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 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 to the BourneShell is a concern, the old syntax should be used. If on the other hand the script requires BASH or KornShell, the new syntax is much more flexible.
See the 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 specific to the compound command which is performed before any ordinary Bash expansions or commands which might be contained. This is for example what allows the Bash test expression's logical and/or operators to be short-circuiting, or the case..esac expression to handle its ;; operator specially. 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.
# Bash (( 0 ? $(echo "This doesn't do what you think..." >&2; echo 1) : 1 )) [[ '1 + 1' -eq 2 && $(echo "...but this probably does what you expect." >&2) ]]
# 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. (( $(print 'x < 1') )) # Valid command, Exit status is 0 [[ $(print 'x -lt 1') ]] # Valid command, But doesn't do what you think. set -x reveals the resulting command is [[ -n 'x -lt 1' ]]