Is double square brackets [[ ]] preferable over single square brackets [ ] in Bash?
A co-worker claimed recently in a code review that the [[ ]]
construct is to be preferred over [ ]
in constructs like
if [ "`id -nu`" = "$someuser" ] ; then
echo "I love you madly, $someuser"
fi
He couldn't provide a rationale. Is there one?
[[
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
[[ ]]
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).
Behavior differences
First, let's analyse the behavior differences between both. Tested in Bash 4.3.11.
POSIX vs Bash extension:
[
is POSIX [[
is a Bash extension regular command vs magic
[
is just a regular command with a weird name.
]
is just an argument of [
that prevents further arguments from being used.
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 makes X
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. &&
and ||
[[ a = a && b = b ]]
: true, logical and [ a = a && b = b ]
: syntax error, &&
parsed as an AND command separator cmd1 && cmd2
[ a = a -ab = b ]
: equivalent, but deprecated by POSIX [ a = a ] && [ b = b ]
: POSIX recommendation (
[[ (a = a || a = b) && a = b ]]
: false [ ( a = a ) ]
: syntax error, ()
is interpreted as a subshell [ ( a = a -oa = b ) -aa = b ]
: equivalent, but ()
is deprecated by POSIX ([ a = a ] || [ a = b ]) && [ a = b ]
POSIX recommendation word splitting
x='a b'; [[ $x = 'ab' ]]
x='a b'; [[ $x = 'ab' ]]
: true, quotes not needed x='a b'; [ $x = 'ab' ]
x='a b'; [ $x = 'ab' ]
: syntax error, expands to [ ab = 'ab' ]
x='a b'; [ "$x" = 'ab' ]
x='a b'; [ "$x" = 'ab' ]
: equivalent =
[[ ab = a? ]]
[[ ab = a? ]]
: true, because it does pattern matching ( * ? [
are magic). Does not glob expand to files in current directory. [ ab = a? ]
[ ab = a? ]
: a?
glob expands. So may be true or false depending on the files in the current directory. [ ab = a? ]
[ ab = a? ]
: false, not glob expansion =
and ==
are the same in both [
and [[
, but ==
is a Bash extension. printf 'ab' | grep -Eq 'a.'
: POSIX ERE equivalent [[ ab =~ 'ab?' ]]
[[ ab =~ 'ab?' ]]
: false, loses magic with ''
[[ ab? =~ 'ab?' ]]
[[ ab? =~ 'ab?' ]]
: true =~
[[ ab =~ ab? ]]
[[ ab =~ ab? ]]
: true, POSIX extended regular expression match, ?
does not glob expand [ a =~ a ]
: syntax error printf 'ab' | grep -Eq 'ab?'
: POSIX equivalent Recommendation
I prefer to always use []
.
There are POSIX equivalents for every [[ ]]
construct I've seen.
If you use [[ ]]
you:
[
is just a regular command with a weird name, no special semantics are involved.