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 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.
[ "$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
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.
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
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.
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!" |
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" |
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 [?
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.