In my recent post I mentioned in passing, that you should be using double brackets [[…]]
for tests in bash
instead of single brackets.
This is the post where I explain why. I also talked about this briefly in my MacSysAdmin session: Scripting Bash
Double Brackets are a bash
ism
Double brackets were originally introduced in ksh
and later adopted by bash
and other shells. To use double brackets your shebang should be #!/bin/bash
not #!/bin/sh
.
Since sh
on macOS is bash
pretending to be sh
, double brackets will still work with the wrong shebang, but then your script might break on other platforms where a different shell might be pretending to be sh
. Consistent behavior across platforms is the main point why sh
is still around, so don’t use double brackets in sh
(or use bash
to use double brackets).
I go into detail on why to use bash
over sh
in this post: On the Shebang
Side note on syntax
In shell scripts you usually use tests in if
or while
clauses. These are tedious to write in the interactive shell. The ‘and’ operator &&
will execute the following statement only if the preceding statement returns 0
(success). So you can use &&
to write simple if … then …
clauses in a single line.
if [ -d Documents ]
then
echo "found docs"
fi
and
[ -d Documents ] && echo "found docs"
have the same effect. The second is much shorter, but as soon as the test or the command gets more complex you should revert to the longer syntax.
Alternatively, the ‘or’ operator ||
will only execute the following statement when the previous statement returns non-zero or fails:
[ -d Documents ] || echo "no docs"
is the same as
if [ ! -d Documents ]
then
echo "no docs"
fi
What’s wrong with the single brackets?
The single bracket [
is actually a command. It has the same functionality as the test
command, except that the last argument needs to be the closing square bracket ]
$ [ -d Documents && echo "found docs"
-bash: [: missing `]'
~ $ [ -d Documents ] && echo "found docs"
found docs
$ test -d Documents && echo "found docs"
found docs
Note: in bash
on macoS both test
and [
are built-in commands, but as usual for built-in commands there are also executables /bin/test
and /bin/[
.
A single bracket test will fail when one of its arguments is empty and gets substituted to nothing:
$ a="abc"
$ b="xyz"
$ [ $a = $b ] || echo "unequal"
unequal
$ unset a
$ [ $a = $b ] || echo "unequal"
-bash: [: =: unary operator expected
unequal
You can prevent this error by quoting the variables (always a prudent solution).
$ [ "$a" = "$b" ] || echo "unequal"
unequal
Double brackets in bash
are not a command but a part of the language syntax. This means they can react more tolerantly to ‘disappearing’ arguments:
$ [[ $a = $b ]] || echo "unequal"
unequal
You will also get an error if one of the arguments is substituted with a value with whitespace with single brackets, while double brackets can deal with this.
$ a="a"
$ b="a space"
$ [ $a = $b ] || echo "unequal"
-bash: [: too many arguments
unequal
$ [[ $a = $b ]] || echo "unequal"
unequal
Note: the =
operator in sh
and bash
is for string comparison. To compare numerical values you need to use the -eq
(equals), -ne
(not equals), -gt
(greater than), -ge
(greater than or equal), -lt
(less than), -le
(less than or equal) operators. With double brackets you can also use two equals characters ==
for a more C like syntax. (or, better, use ((…))
syntax for arithmetic expressions)
Also, when using the =
to assign variables, you cannot have spaces before and after the =
, while the spaces are required for the comparison operator (both with single and double brackets):
a="a" # no spaces
b="b" # no spaces
[ "$a" = "$b" ] # spaces!
[[ $a = $b ]] # spaces!
Since the single bracket is a command, many characters it uses for its arguments need to be escaped to work properly:
$ [ ( "$a" = "$b" ) -o ( "$a" = "$c" ) ]
-bash: syntax error near unexpected token `"$a"'
$ [ \( "$a" = "$b" \) -o \( "$a" = "$c" \) ]
You could alternatively split this example into two tests: [ "$a" = "$b" ] || [ "$a" = "$c" ]
.
Double brackets interpret these characters properly. You can also use the (again more C like) &&
and ||
operators instead of -a
and -o
.
[[ ( $a = $b ) || ( $a = $c ) ]]
In general, you can work around most of the issues with single bracket syntax, but the double bracket syntax is more straight forward and hence more legible and easier to type.
Double bracket features
Aside from the cleaner syntax, there are a few ‘bonus’ features you gain with double brackets.
With double brackets you can compare to *
and ?
wildcards, and bracket globbing […]
:
$ a="Documents"
$ [[ $a = D* ]] && echo match
match
$ a=hat
$ [[ $a = ?at ]] && echo match
match
$ [[ $a = [chrp]at ]] && echo match
match
You can also use <
and >
to compare strings lexicographically:
$ a=cat
$ b=hat
$ [[ $a < $b ]] && echo sorted
sorted
And you get an operator =~
for regular expressions:
$ a=cat
$ b="the cat in the hat"
$ [[ $a =~ ^.at ]] && echo match
match
$ [[ $b =~ ^.at ]] && echo match
Note that you should not quote the globbing patterns or the regex pattern.
Summary
- you should use
bash
for shell scripting on macOS - when using bash, you should use double brackets instead of single brackets
- double brackets are safer, easier to type and read, and also add few neat features
Great explanation. I started using double brackets because of regular expressión, and now I always use it.
Thanks, exactly what I needed!
thanks for the briefing