Is double square brackets [[ ]] preferable over single square brackets [ ] in Bash?
[[
has fewer surprises and is generally safer to use. But it is not portable - POSIX doesn't specify what it does and only some shells support it (beside bash, I heard ksh supports it too). For example, you can do
[[ -e $b ]]
to test whether a file exists. But with [
, you have to quote $b
, because it splits the argument and expands things like "a*"
(where [[
takes it literally). That has also to do with how [
can be an external program and receives its argument just normally like every other program (although it can also be a builtin, but then it still has not this special handling).
[[
also has some other nice features, like regular expression matching with =~
along with operators like they are known in C-like languages. Here is a good page about it: What is the difference between test, [
and [[
? and Bash Tests
Behavior differences
Some differences on Bash 4.3.11:
POSIX vs Bash extension:
[
is POSIX[[
is a Bash extension inspired from Korn shell
regular command vs magic
[
is just a regular command with a weird name.]
is just the last argument of[
.
Ubuntu 16.04 actually has an executable for it at
/usr/bin/[
provided by coreutils, but the bash built-in version takes precedence.Nothing is altered in the way that Bash parses the command.
In particular,
<
is redirection,&&
and||
concatenate multiple commands,( )
generates subshells unless escaped by\
, and word expansion happens as usual.[[ X ]]
is a single construct that makesX
be parsed magically.<
,&&
,||
and()
are treated specially, and word splitting rules are different.There are also further differences like
=
and=~
.
In Bashese:
[
is a built-in command, and[[
is a keyword: https://askubuntu.com/questions/445749/whats-the-difference-between-shell-builtin-and-shell-keyword<
[[ a < b ]]
: lexicographical comparison[ a \< b ]
: Same as above.\
required or else does redirection like for any other command. Bash extension.expr x"$x" \< x"$y" > /dev/null
or[ "$(expr x"$x" \< x"$y")" = 1 ]
: POSIX equivalents, see: How to test strings for lexicographic less than or equal in Bash?
&&
and||
[[ a = a && b = b ]]
: true, logical and[ a = a && b = b ]
: syntax error,&&
parsed as an AND command separatorcmd1 && cmd2
[ a = a ] && [ b = b ]
: POSIX reliable equivalent[ a = a -a b = b ]
: almost equivalent, but deprecated by POSIX because it is insane and fails for some values ofa
orb
like!
or(
which would be interpreted as logical operations
(
[[ (a = a || a = b) && a = b ]]
: false. Without( )
, would be true because[[ && ]]
has greater precedence than[[ || ]]
[ ( a = a ) ]
: syntax error,()
is interpreted as a subshell[ \( a = a -o a = b \) -a a = b ]
: equivalent, but()
,-a
, and-o
are deprecated by POSIX. Without\( \)
would be true because-a
has greater precedence than-o
{ [ a = a ] || [ a = b ]; } && [ a = b ]
non-deprecated POSIX equivalent. In this particular case however, we could have written just:[ a = a ] || [ a = b ] && [ a = b ]
because the||
and&&
shell operators have equal precedence unlike[[ || ]]
and[[ && ]]
and-o
,-a
and[
word splitting and filename generation upon expansions (split+glob)
x='a b'; [[ $x = 'a b' ]]
: true, quotes not neededx='a b'; [ $x = 'a b' ]
: syntax error, expands to[ a b = 'a b' ]
x='*'; [ $x = 'a b' ]
: syntax error if there's more than one file in the current directory.x='a b'; [ "$x" = 'a b' ]
: POSIX equivalent
=
[[ ab = a? ]]
: true, because it does pattern matching (* ? [
are magic). Does not glob expand to files in current directory.[ ab = a? ]
:a?
glob expands. So may be true or false depending on the files in the current directory.[ ab = a\? ]
: false, not glob expansion=
and==
are the same in both[
and[[
, but==
is a Bash extension.case ab in (a?) echo match; esac
: POSIX equivalent[[ ab =~ 'ab?' ]]
: false, loses magic with''
in Bash 3.2 and above and provided compatibility to bash 3.1 is not enabled (like withBASH_COMPAT=3.1
)[[ ab? =~ 'ab?' ]]
: true
=~
[[ ab =~ ab? ]]
: true, POSIX extended regular expression match,?
does not glob expand[ a =~ a ]
: syntax error. No bash equivalent.printf 'ab\n' | grep -Eq 'ab?'
: POSIX equivalent (single line data only)awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?'
: POSIX equivalent.
Recommendation: always use []
There are POSIX equivalents for every [[ ]]
construct I've seen.
If you use [[ ]]
you:
- lose portability
- force the reader to learn the intricacies of another bash extension.
[
is just a regular command with a weird name, no special semantics are involved.
Thanks to Stéphane Chazelas for important corrections and additions.
[[ ]]
has more features - I suggest you take a look at the Advanced Bash Scripting Guide for more info, specifically the extended test command section in Chapter 7. Tests.
Incidentally, as the guide notes, [[ ]]
was introduced in ksh88 (the 1988 version of the Korn shell).