245

I'm reading bash examples about if, but some examples are written with single square brackets:

if [ -f $param ] then #... fi 

others with double square brackets:

if [[ $? -ne 0 ]] then start looking for errors in yourlog fi 

What is the difference?

4

7 Answers 7

278

Single [] are posix shell compliant condition tests.

Double [[]] are an extension to the standard [] and are supported by bash and other shells (e.g. zsh, ksh). They support extra operations (as well as the standard posix operations). For example: || instead of -o and regex matching with =~. A fuller list of differences can be found in the bash manual section on conditional constructs.

Use [] whenever you want your script to be portable across shells. Use [[]] if you want conditional expressions not supported by [] and don't need to be portable.

Sign up to request clarification or add additional context in comments.

5 Comments

I'd add that if your script doesn't start with a shebang that explicitly requests a shell that supports [[ ]] (e.g. bash with #!/bin/bash or #!/usr/bin/env bash), you should use the portable option. Scripts that assume /bin/sh supports extensions like this will break on OSes like recent Debian and Ubuntu releases where that's not the case.
@GordonDavisson Is there any good reason for a script to not start with a shebang? I can't imagine switching to use the (IMO) awful [ syntax just to avoid adding a shebang that should probably be there anyways.
@CoryGross The only situation I can see omitting the shebang line is in scripts that need to be run with source or ., so they run in the parent shell and any shebang will be ignored (though even there I sometimes add a "protective shebang" like #!/bin/echo You need to run this script with the source command to print an error message in case someone runs it normally). But be aware that running a script like sh scriptname will override the shebang (e.g. see this question).
That's not really a reason not to have a shebang though. It's just a situationn where you could get away with not having a shebang if you wanted to for some reason, but what that reason would be I still don't know. If you have a policy of just always starting your scripts with a shebang then everything still works. I have the same shebang in the scripts that I source as the ones that I execute. It's ignored when it's not needed and it's used when it is. If you just do that then there's no need to think about, rememeber, or even know in the first place that shebangs are omitted when sourced.
@GordonDavisson Thinking about it. I guess techinically your "protective shebang" pattern could conceivably be considered as a reason for someone breaking my policy of always adding the same shebang without exception, but ammusingly enough you still end up with a shebang despite that, so the question of whether to use [ or [[ is unaffected. Although, I imagine (though don't immediately know) that there is some other (likely many) way(s) to check within a script whether it is being sourced or executed in a way that is just as reliable and quite possibly more explicit / with clearer intent.
146

Behavior differences

Tested in Bash 4.3.11:

  • POSIX vs Bash extension:

  • 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: https://askubuntu.com/questions/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && 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 || 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 needed
    • x='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 with BASH_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.

7 Comments

"There are POSIX equivalents for every [[ ]] construct I've seen." The same thing can be said of any Turing Complete language on the face of the planet.
@A.Rick that would be a valid answer to all "How to do X in language Y" SO questions :-) Of course, there is a "conveniently" implicit in that statement.
Fantastic summary. Thanks for the effort. I however disagree with the recommendation. It's portability versus more consistent and powerful syntax. If you can require bash >4 in your environments then the [[ ]] syntax is recommended.
Great answer but I think the Recommendation: always use [] should be read as My preference: use [] if you don't want to lose portability. As stated here: If portability/conformance to POSIX or the BourneShell is a concern, the old syntax should be used. If on the other hand the script requires BASH, Zsh, or KornShell, the new syntax is usually more flexible, but not necessarily backwards compatible. I'd rather go with [[ ab =~ ab? ]] if I can and have no concern about backward compatibility than printf 'ab' | grep -Eq 'ab?'
Great summary. In addition, filename expansion doesn't happen inside [[ ]]
|
23

[[ is a bash keyword similar to (but more powerful than) the [ command.

See

http://mywiki.wooledge.org/BashFAQ/031 and http://mywiki.wooledge.org/BashGuide/TestsAndConditionals

Unless you're writing for POSIX sh, I recommend [[.

1 Comment

The team from [email protected], that manage bash FAQ, Greg wiki, bash-hackers and shellcheck.net Anyway replaced we by I.
17

Inside single brackets for condition test (i.e. [ ... ]), some operators such as single = is supported by all shells, whereas use of operator == is not supported by some of the older shells.

Inside double brackets for condition test (i.e. [[ ... ]]), there is no difference between using = or == in old or new shells.

Edit: I should also note that: In bash, always use double brackets [[ ... ]] if possible, because it is safer than single brackets. I'll illustrate why with the following example:

if [ $var == "hello" ]; then 

if $var happens to be null / empty, then this is what the script sees:

if [ == "hello" ]; then 

which will break your script. The solution is to either use double brackets, or always remember to put quotes around your variables ("$var"). Double brackets is better defensive coding practice.

1 Comment

Putting quotes around all reads of variables unless you have a very good reason not to is a much better defensive coding practice, since it applies to all reads of variables, not just those in conditions. An iTunes installer bug once deleted people's files if the hard drive name contained spaces (or something like that). It also solves the problem you mention.
8
  • [ is a builtin like printf. Bash syntax expect to see it at the same place as commands. And ] is nothing to Bash except the fact that it is expected by the [ builtin. (man bash / SHELL BUILTIN COMMANDS)
  • [[ is a keyword like if. Bash syntax starts also expect it at the same place as command but instead of executing it, it enters the conditional context. And ]] is also a keyword ending this context. (man bash / SHELL GRAMMAR / Compound Commands)

In order, bash tries to parse: Syntax Keywords > User Alias > Builtin Function > User Function > Command in $PATH

type [ # [ is a shell builtin type [[ # [[ is a shell keyword type ] # bash: type: ]: not found type ]] # ]] is a shell keyword compgen -k # Keywords: if then else ... compgen -b # Builtins: . : [ alias bg bind ... which [ # /usr/bin/[ 
  • [ is slower <= it executes more parsing code I guess. But I know that it calls the same number of syscall (tested with
  • [[ is syntactically easier to parse even for human as it starts a context. For arithmetical condition, think about using ((.
time for i in {1..1000000}; do [ 'a' = 'b' ] ; done # 1.990s time for i in {1..1000000}; do [[ 'a' == 'b' ]] ; done # 1.371s time for i in {1..1000000}; do if [ 'a' = 'a' ]; then if [ 'a' = 'b' ];then :; fi; fi ; done # 3.512s time for i in {1..1000000}; do if [[ 'a' == 'a' ]]; then if [[ 'a' == 'b' ]];then :; fi; fi; done # 2.143s strace -cf bash -c "for i in {1..100000}; do if [ 'a' = 'a' ]; then if [ 'a' = 'b' ];then :; fi; fi ; done;" # 399 strace -cf bash -c "for i in {1..100000}; do if [[ 'a' == 'a' ]]; then if [[ 'a' == 'b' ]];then :; fi; fi ; done;" # 399 

I recommend using [[: If you do not explicitly care about posix compatibility, it means that you are not, so do no care about getting "more" compatible a script that is not.

Comments

3

you can use the double square brackets for light regex matching, e.g. :

if [[ $1 =~ "foo.*bar" ]] ; then

(as long as the version of bash you are using supports this syntax)

2 Comments

Except you've quoted the pattern, so it's now treated as a literal string.
very true. sometimes this annoys me :)
1

Bash manual says:

When used with [[, the ‘<’ and ‘>’ operators sort lexicographically using the current locale. The test command uses ASCII ordering.

(The test command is identical to [ ] )

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.