====== BASH - Brackets ======
What is the difference between single and double square brackets in bash?
[ $STRING != foo ]
and
[[ $STRING != foo ]]
Both **[** and **[[** are used to evaluate expressions.
* **[** is a "test" command, available in POSIX shells.
* **[[** is a "newer test" command; and works only in more modern shells, such as Bash, Zsh and the Korn shell, and is more powerful.
----
===== [ =====
[
**[** is a builtin in Bash and many other modern shells.
The builtin **[** is similar to **test** with the additional requirement of a closing **]**.
The builtins [ and test imitate the functionality /bin/[ and /bin/test along with their limitations so that scripts would be backwards compatible.
The original executables still exist mostly for POSIX compliance and backwards compatibility.
----
==== Examples ====
[ "$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
----
==== Use BASH to check [ ====
Running the command:
type [
returns:
[ is a shell builtin
This indicates that **[** is interpreted as a builtin by default.
**NOTE:** [ only looks for executables on the PATH and is equivalent to **type -p [**
----
===== [[ =====
[[
**[[** is not as compatible, it won't necessarily work with whatever /bin/sh points to.
So [[ is the more modern Bash / Zsh / Ksh option.
Because [[ is built into the shell and does not have legacy requirements, you don't need to worry about word splitting based on the IFS variable to mess up on variables that evaluate to a string with spaces. Therefore, you don't really need to put the variable in double quotes.
----
==== Example ====
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: $file" >&2
fi
if [[ $file0 -nt $file1 ]]; then
printf 'file %s is newer than %s\n' "$file0" "$file1"
fi
----
==== Use BASH to check [[ ====
Running the command
type [[
returns:
[[ is a shell keyword
**NOTE:** Because [[ is built into the shell and does not have legacy requirements, you don't need to worry about word splitting based on the IFS variable to mess up on variables that evaluate to a string with spaces. Therefore, you don't really need to put the variable in double quotes.
----
===== Comparisons between [ and [[ =====
----
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^
|string comparison|>|\> (*)|[[ a > b ]] || echo "a does not come after 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) ]] &&
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"|
----
===== More subtle differences. =====
==== Quoting of arguments ====
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.
----
==== Escaping of Parentheses ====
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.
----
===== Rule of Thumb =====
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 Bourne Shell 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.
**NOTE:** Any problem with an operator used with **[[** resulting in an unhandleable parse-time error will cause bash to terminate, even if the command is never evaluated.