Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between single and double square brackets in Bash

People also ask

Why do we use double brackets in shell script?

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.

What does double parentheses mean in bash?

construct permits arithmetic expansion and evaluation. In its simplest form, a=$(( 5 + 3 )) would set a to 5 + 3, or 8. However, this double-parentheses construct is also a mechanism for allowing C-style manipulation of variables in Bash, for example, (( var++ )). Example 8-5.

What does || in bash mean?

Logical OR Operator ( || ) in Bash It is usually used with boolean values and returns a boolean value. It returns true if at least one of the operands is true. Returns false if all values are false.


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.


Behavior differences

Tested in Bash 4.3.11:

  • POSIX vs Bash extension:

    • [ is POSIX
    • [[ is a Bash extension inspired from Korn shell
  • 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

  • <

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


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.


[[ 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, we recommend [[.


  • [ 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 syntaxly easyer 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 explicitely care about posix compatibility, it means that you are not, so do no care about getting "more" compatible a script that is not.


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)