Difference between single and double square brackets in Bash
Asked Answered
W

7

231

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?

Wiredraw answered 24/11, 2012 at 15:56 Comment(4)
You can get your answer by looking at answer of this question: unix.stackexchange.com/questions/3831/…Graubert
See also serverfault.com/questions/52034/…Byrnie
Tangentially, regarding the second example, see also Why is testing ”$?” to see if a command succeeded or not, an anti-pattern?Chuckchuckfull
See also Difference between sh and bashChuckchuckfull
B
267

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.

Barthold answered 24/11, 2012 at 15:59 Comment(3)
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.Eyecatching
@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.Dare
@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).Eyecatching
F
137

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.

Freeland answered 12/7, 2015 at 10:21 Comment(8)
"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.Annuity
@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.Freeland
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.Ellissa
@Downvoters please explain so I can learn and improve info :-)Freeland
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?'Rex
Great summary. In addition, filename expansion doesn't happen inside [[ ]]Likelihood
I agree with @MauricioRobayo, for example if you write [[ 100 > 90 ]] && echo OK it wont work, while [ 100 -gt 90 ] && echo OK does. I don't know why but [[ ]] does not seem very reliable.Bajaj
@Bajaj that's because [[ ]] does lexicographical comparison, and 1 < 9. For numbers you need instead (( 100 > 90 )) && echo OK.Freeland
A
21

[[ 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 [[.

Aglitter answered 24/11, 2012 at 16:17 Comment(1)
The team from [email protected], that manage bash FAQ, Greg wiki, bash-hackers and shellcheck.net Anyway replaced we by I.Consciousness
H
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.

Hospital answered 24/11, 2012 at 15:59 Comment(1)
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.Inhumanity
C
7
  • [ 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.

Cantor answered 26/6, 2021 at 22:8 Comment(0)
H
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)

Heerlen answered 24/11, 2012 at 15:58 Comment(2)
Except you've quoted the pattern, so it's now treated as a literal string.Flavorsome
very true. sometimes this annoys me :)Heerlen
U
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 [ ] )

Usury answered 28/10, 2015 at 23:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.