A coworker 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?
[[
with it the code is good and clear, but remember that day when you'll port your scriptworks on the system with default shell which is not bash
or ksh
, etc. [
is uglier, cumbersome, but works as AK-47
in any situation.
[[
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 KornShell
[ is POSIX
[[ is a Bash extension inspired from KornShell
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 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: What's the difference between shell builtin and shell keyword?
[ 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 makes X be parsed magically. <, &&, || and () are treated specially, and word splitting rules are different. There are also further differences like = and =~.
< [[ 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?
[[ 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 separator cmd1 && 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 of a or b like ! or ( which would be interpreted as logical operations
[[ a = a && b = b ]]: true, logical and
[ a = a && b = b ]: syntax error, && parsed as an AND command separator cmd1 && 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 of a or b like ! or ( which would be interpreted as logical operations
( [[ (a = a || a = b) && a = b ]]: false. Without ( ) it 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 \( \) it 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 [
[[ (a = a || a = b) && a = b ]]: false. Without ( ) it 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 \( \) it 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 are not needed x='a b'; [ $x = 'a b' ]: syntax error. It 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
x='a b'; [[ $x = 'a b' ]]: true. Quotes are not needed
x='a b'; [ $x = 'a b' ]: syntax error. It 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 the current directory. [ ab = a? ]: a? glob expands. So it 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 with BASH_COMPAT=3.1) [[ ab? =~ 'ab?' ]]: true
[[ ab = a? ]]: true, because it does pattern matching (* ? [ are magic). Does not glob expand to files in the current directory.
[ ab = a? ]: a? glob expands. So it 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 with BASH_COMPAT=3.1)
[[ ab? =~ 'ab?' ]]: true
=~ [[ ab =~ ab? ]]: true. POSIX extended regular expression match and ? 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.
[[ ab =~ ab? ]]: true. POSIX extended regular expression match and ? 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, and no special semantics are involved.
Thanks to Stéphane Chazelas for important corrections and additions.
[[
would presumably be a regular command too maybe). Related: askubuntu.com/questions/766270/…
>
which also is redirection
and greater than
[
compared to the quite sensible behaviors of [[
and come away with the recommendation to use [
based solely on portability (which is rarely a concern these days) and that people have to learn the "intricacies" of the bash version. It's more like you have to learn the absurd intricacies of the legacy version. The bash version does what you expect from most other programming languages.
[[ ]]
has more features - I suggest you take a look at the Advanced Bash Scripting Guide for more information, specifically the extended test command section in Chapter 7. Tests.
Incidentally, as the guide notes, [[ ]]
was introduced in ksh88 (the 1988 version of KornShell).
From Which comparator, test, bracket, or double bracket, is fastest?:
The double bracket is a “compound command” where as test and the single bracket are shell built-ins (and in actuality are the same command). Thus, the single bracket and double bracket execute different code. The test and single bracket are the most portable as they exist as separate and external commands. However, if your using any remotely modern version of BASH, the double bracket is supported.
[[
might bring. But then, I'm an old school old fart :-)
[
and test
even though external versions also exist.
PS1=...crazy stuff...
and/or $PROMPT_COMMAND
); for these, I don't want any perceptible delay in the execution of the script.
apt-get update
if it's been more than X hours since it was last run. It is a great relief when one can leave portability off the already-too-long list of constraints for code.
If you are into following Google's style guide:
Test, [ … ], and [[ … ]] [[ … ]] is preferred over [ … ], test and /usr/bin/[. [[ … ]] reduces errors as no pathname expansion or word splitting takes place between [[ and ]]. In addition, [[ … ]] allows for regular expression matching, while [ … ] does not. # This ensures the string on the left is made up of characters in # the alnum character class followed by the string name. # Note that the RHS should not be quoted here. if [[ "filename" =~ ^[[:alnum:]]+name ]]; then echo "Match" fi # This matches the exact pattern "f*" (Does not match in this case) if [[ "filename" == "f*" ]]; then echo "Match" fi # This gives a "too many arguments" error as f* is expanded to the # contents of the current directory if [ "filename" == f* ]; then echo "Match" fi For the gory details, see E14 at http://tiswww.case.edu/php/chet/bash/FAQ
[[ -d ~ ]]
returns true (which implies ~
was expanded to /home/user
). I think Google's should have been more precise in it's writing.
In a question tagged 'bash' that explicitly has "in Bash" in the title, I'm a little surprised by all of the replies saying you should avoid [[
...]]
because it only works in bash!
It's true that portability is the primary objection: if you want to write a shell script which works in Bourne-compatible shells even if they aren't bash, you should avoid [[
...]]
. (And if you want to test your shell scripts in a more strictly POSIX shell, I recommend dash
; though it is an incomplete POSIX implementation since it lacks the internationalization support required by the standard, it also lacks support for the many non-POSIX constructs found in bash, ksh, zsh, etc.)
The other objection I see is at least applicable within the assumption of bash: that [[
...]]
has its own special rules which you have to learn, while [
...]
acts like just another command. That is again true (and Mr. Santilli brought the receipts showing all the differences), but it's rather subjective whether the differences are good or bad. I personally find it freeing that the double-bracket construct lets me use (
...)
for grouping, &&
and ||
for Boolean logic, <
and >
for comparison, and unquoted parameter expansions. It's like its own little closed-off world where expressions work more like they do in traditional, non-command-shell programming languages.
A point I haven't seen raised is that this behavior of [[
...]]
is entirely consistent with that of the arithmetic expansion construct $((
...))
, which is specified by POSIX, and also allows unquoted parentheses and Boolean and inequality operators (which here perform numeric instead of lexical comparisons). Essentially, any time you see the doubled bracket characters you get the same quote-shielding effect.
(Bash and its modern relatives also use ((
...))
– without the leading $
– as either a C-style for
loop header or an environment for performing arithmetic operations; neither syntax is part of POSIX.)
So there are some good reasons to prefer [[
...]]
; there are also reasons to avoid it, which may or may not be applicable in your environment. As to your coworker, "our style guide says so" is a valid justification, as far as it goes, but I'd also seek out backstory from someone who understands why the style guide recommends what it does.
A typical situation where you cannot use [[
is in an autotools configure.ac script. There brackets have a special and different meaning, so you will have to use test
instead of [
or [[
-- Note that test and [
are the same program.
[
to be defined as a POSIX shell function?
[[ ]] double brackets are unsupported under certain versions of SunOS and totally unsupported inside function declarations by:
GNU Bash, version 2.02.0(1)-release (sparc-sun-solaris2.6)
In a nutshell, [[ is better because it doesn't fork another process. No brackets or a single bracket is slower than a double bracket because it forks another process.
type [
to see this.
[[
, as distinct from [
, is syntax interpreted by the bash command-line interpreter. In bash, try typing type [[
. unix4linux is correct that although classic Bourne-shell [
tests fork off a new process to determine the truth value, the [[
syntax (borrowed from ksh by bash, zsh, etc) does not.
[
is built-in to Bash as well as Dash (the /bin/sh
in all Debian-derived Linux distributions).
Success story sharing
[[ ]]
but interprets it as meaning the same as[ ]
.#!/bin/sh
but then switch them to use#!/bin/bash
as soon as I rely on some BASH specific feature, to denote it is no longer Bourne shell portable.