I am a little bit confused on what do these operators do differently when used in bash (brackets, double brackets, parenthesis and double parenthesis).

[[ , [ , ( , ((

I have seen people use them on if statements like this :

if [[condition]]

if [condition]

if ((condition))

if (condition)

In Bourne-like shells, an if statement typically looks like


The then clause is executed if the exit code of the command-list1 list of commands is zero. If the exit code is nonzero, then the else clause is executed. command-list1 can be simple or complex. It can, for example, be a sequence of one or more pipelines separated by one of the operators ;, &, &&, || or newline. The if conditions shown below are just special cases of command-list1:

  1. if [ condition ]

    [ is another name for the traditional test command. [ / test is a standard POSIX utility. All POSIX shells have it builtin (though that's not required by POSIX²). The test command sets an exit code and the if statement acts accordingly. Typical tests are whether a file exists or one number is equal to another.

  2. if [[ condition ]]

    This is a new upgraded variation on test¹ from ksh that bash, zsh, yash, busybox sh also support. This [[ ... ]] construct also sets an exit code and the if statement acts accordingly. Among its extended features, it can test whether a string matches a wildcard pattern (not in busybox sh).

  3. if ((condition))

    Another ksh extension that bash and zsh also support. This performs arithmetic. As the result of the arithmetic, an exit code is set and the if statement acts accordingly. It returns an exit code of zero (true) if the result of the arithmetic calculation is nonzero. Like [[...]], this form is not POSIX and therefore not portable.

  4. if (command)

    This runs command in a subshell. When command completes, it sets an exit code and the if statement acts accordingly.

    A typical reason for using a subshell like this is to limit side-effects of command if command required variable assignments or other changes to the shell's environment. Such changes do not remain after the subshell completes.

  5. if command

    command is executed and the if statement acts according to its exit code.

¹ though not really a command but a special shell construct with its own separate syntax from that of normal command, and varying significantly between shell implementations

² POSIX does require that there be a standalone test and [ utilities on the system however, though in the case of [, several Linux distributions have been known to be missing it.

| improve this answer | |
  • 47
    Thanks for including the 5th option. That's key to understanding how this actually works and is surprisingly underutilized. – chicks Aug 30 '16 at 19:32
  • 10
    Note that [ is actually a binary, not a internal command or symbol. Generally lives in /bin. – Julien R. Sep 3 '16 at 12:17
  • 11
    @JulienR. actually [ is a built in, as is test. There are binary versions available for compatibility reasons. Check out help [ and help test. – OldTimer Sep 20 '16 at 4:23
  • 9
    Worth noting that while (( isnt POSIX, $(( i.e. arithmetic expansion is and it's easy to confuse them. Often a workaround is to use something like [ $((2+2)) -eq 4 ] to make use of arithmetic in conditinal statements – Sergiy Kolodyazhnyy Nov 30 '17 at 18:10
  • 4
    I wish I could up-vote this answer more than once. Perfect explanation. – Anthony Gatlin Oct 9 '19 at 11:33
  • (…) parentheses indicate a subshell. What's inside them isn't an expression like in many other languages. It's a list of commands (just like outside parentheses). These commands are executed in a separate subprocess, so any redirection, assignment, etc. performed inside the parentheses has no effect outside the parentheses.
    • With a leading dollar sign, $(…) is a command substitution: there is a command inside the parentheses, and the output from the command is used as part of the command line (after extra expansions unless the substitution is between double quotes, but that's another story).
  • { … } braces are like parentheses in that they group commands, but they only influence parsing, not grouping. The program x=2; { x=4; }; echo $x prints 4, whereas x=2; (x=4); echo $x prints 2. (Also braces being keywords need to be delimited and found in command position (hence the space after { and the ; before }) whereas parentheses don't. That's just a syntax quirk.)
    • With a leading dollar sign, ${VAR} is a parameter expansion, expanding to the value of a variable, with possible extra transformations. The ksh93 shell also supports ${ cmd;} as form of command substitution that doesn't spawn a subshell.
  • ((…)) double parentheses surround an arithmetic instruction, that is, a computation on integers, with a syntax resembling other programming languages. This syntax is mostly used for assignments and in conditionals. This only exists in ksh/bash/zsh, not in plain sh.
    • The same syntax is used in arithmetic expressions $((…)), which expand to the integer value of the expression.
  • [ … ] single brackets surround conditional expressions. Conditional expressions are mostly built on operators such as -n "$variable" to test if a variable is empty and -e "$file" to test if a file exists. Note that you need a space around each operator (e.g. [ "$x" = "$y" ], not [ "$x"="$y" ]), and a space or a character like ; both inside and outside the brackets (e.g. [ -n "$foo" ], not [-n "$foo"]).
  • [[ … ]] double brackets are an alternate form of conditional expressions in ksh/bash/zsh with a few additional features, for example you can write [[ -L $file && -f $file ]] to test if a file is a symbolic link to a regular file whereas single brackets require [ -L "$file" ] && [ -f "$file" ]. See Why does parameter expansion with spaces without quotes works inside double brackets [[ but not single brackets [? for more on this topic.

In the shell, every command is a conditional command: every command has a return status which is either 0 indicating success or an integer between 1 and 255 (and potentially more in some shells) indicating failure. The [ … ] command (or [[ … ]] syntax form) is a particular command which can also be spelled test … and succeeds when a file exists, or when a string is non-empty, or when a number is smaller than another, etc. The ((…)) syntax form succeeds when a number is nonzero. Here are a few examples of conditionals in a shell script:

  • Test if myfile contains the string hello:

    if grep -q hello myfile; then …
  • If mydir is a directory, change to it and do stuff:

    if cd mydir; then
      echo "Creating mydir/myfile"
      echo 'some content' >myfile
      echo >&2 "Fatal error. This script requires mydir to exist."
  • Test if there is a file called myfile in the current directory:

    if [ -e myfile ]; then …
  • The same, but also including dangling symbolic links:

    if [ -e myfile ] || [ -L myfile ]; then …
  • Test if the value of x (which is assumed to be numeric) is at least 2, portably:

    if [ "$x" -ge 2 ]; then …
  • Test if the value of x (which is assumed to be numeric) is at least 2, in bash/ksh/zsh:

    if ((x >= 2)); then …
| improve this answer | |
  • Note that single bracket supports the -a instead of &&, so one can write: [ -L $file -a -f $file ], which is the same number of characters within the brackets without the extra [ and ]... – Alexis Wilke Aug 27 '16 at 23:34
  • 7
    @AlexisWilke The operators -a and -o are problematic because they can lead to incorrect parses if some of the operands involved look like operators. That's why I don't mention them: they have zero advantage and don't always work. And never write unquoted variable expansions without a good reason: [[ -L $file -a -f $file ]] is fine but with single brackets you need [ -L "$file" -a -f "$file" ] (which is ok e.g. if $file always starts with / or ./). – Gilles 'SO- stop being evil' Aug 27 '16 at 23:38
  • Note that it's [[ -L $file && -f $file ]] (no -a with the [[...]] variant). – Stéphane Chazelas Nov 30 '17 at 15:25

[ vs [[

This answer will cover the [ vs [[ subset of the question.

Some differences on Bash 4.3.11:

  • POSIX vs Bash extension:

  • regular command vs magic

    • [ is just a regular command with a weird name.

      ] is just an argument of [ that prevents further arguments from being used.

      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 -a b = b ]: equivalent, but deprecated by POSIX³
    • [ a = a ] && [ b = b ]: POSIX and reliable equivalent
  • (

    • [[ (a = a || a = b) && a = b ]]: false
    • [ ( a = a ) ]: syntax error, () is interpreted as a subshell
    • [ \( a = a -o a = b \) -a a = b ]: equivalent, but () is deprecated by POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] POSIX equivalent5
  • 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?' ]]: false4, loses magic with ''
    • [[ 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.

¹ Inspired from the equivalent [[...]] construct in the Korn shell

² but fails for some values of a or b (like + or index) and does numeric comparison if a and b look like decimal integers. expr "x$a" '<' "x$b" works around both.

³ and also fails for some values of a or b like ! or (.

4 in bash 3.2 and above and provided compatibility to bash 3.1 is not enabled (like with BASH_COMPAT=3.1)

5 though the grouping (here with the {...;} command group instead of (...) which would run an unnecessary subshell) is not necessary as the || and && shell operators (as opposed to the || and && [[...]] operators or the -o/-a [ operators) have equal precedence. So [ a = a ] || [ a = b ] && [ a = b ] would be equivalent.

| improve this answer | |
  • @StéphaneChazelas thanks for the info! I've addedexpr to the answer. The term "Bash extension" is not meant to imply Bash was the first shell to add some syntax, learning POSIX sh vs Bash is already enough to drive me crazy. – Ciro Santilli 郝海东冠状病六四事件法轮功 Feb 5 '19 at 9:11
  • 2
    See man test if you tried man [ and got lost. That will explain the POSIX variant. – Jonathan Komar Jun 4 '19 at 7:25

From the bash documentation:

(list) list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list.

In other words, you make sure that whatever happens in 'list' (like a cd) has no effect outside of the ( and ). The only thing that will leak is the exit code of the last command or with set -e the first command that generates an error (other than a few such as if, while, etc.)

((expression)) The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION. If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent to let "expression".

This is a bash extension allowing you to do math. This is somewhat similar to using expr without all the limitations of expr (such as having spaces everywhere, escaping *, etc.)

[[ expression ]] Return a status of 0 or 1 depending on the evaluation of the conditional expression expression. Expressions are composed of the primaries described below under CONDITIONAL EXPRESSIONS. Word splitting and pathname expansion are not performed on the words between the [[ and ]]; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and quote removal are performed. Conditional operators such as -f must be unquoted to be recognized as primaries.

When used with [[, the < and > operators sort lexicographically using the current locale.

This offers an advanced test to compare strings, numbers, and files a bit like test offers, but more powerful.

[ expr ] Return a status of 0 (true) or 1 (false) depending on the evaluation of the conditional expression expr. Each operator and oper and must be a separate argument. Expressions are composed of the primaries described above under CONDITIONAL EXPRESSIONS. test does not accept any options, nor does it accept and ignore an argument of -- as signifying the end of options.


This one calls test. Actually, in the old days, [ was a symbolic link to test. It works the same way and you have the same limitations. Since a binary knows the name with which it was started, the test program can parse parameters until it finds a parameter ]. Fun Unix tricks.

Note that in case of bash, [ and test are built-in functions (as mentioned in a comment), yet pretty much the same limitations apply.

| improve this answer | |
  • 1
    Though test and [ are of course builtin commands in Bash, but it's likely that an external binary exists too. – ilkkachu Aug 27 '16 at 19:12
  • 1
    The external binary for [ is not a symbolic link to test on most modern systems. – Random832 Aug 27 '16 at 19:16
  • 1
    Somehow, I find it amusing that they bother to create two separate binaries, which both have just exactly what they need, instead of just combining them and adding a couple of conditionals. Though actually strings /usr/bin/test shows it has the help text too, so I don't know what to say. – ilkkachu Aug 27 '16 at 19:50
  • 2
    @Random832 I get your point about the GNU rationale to avoid unexpected arg0 behavior but about POSIX requirements, I wouldn't be so affirmative. While the test command is obviously required to exist as a standalone file based command by the standard, nothing in it states its [ variant need to be implemented that way too. For example, Solaris 11 doesn't provide any [ executable but is nevertheless fully compliant with the POSIX standards – jlliagre Aug 28 '16 at 4:00
  • 3
    (exit 1) has an effect outside of the parentheses. – Alexander Feb 10 '18 at 8:23

Some examples:

Traditional test:

foo="some thing"
# check if value of foo is not empty
if [ -n "$foo" ] ; then... 
if test -n "$foo" ; then... 

test and [ are commands like any others, so the variable is split into words unless it's in quotes.

New-style test

[[ ... ]] is a (newer) special shell construct, which works a bit differently, the most obvious thing being that it doesn't word-split variables:

if [[ -n $foo ]] ; then... 

Some documentation on [ and [[ here.

Arithmetic test:

foo=12 bar=3
if (( $foo + $bar == 15 )) ; then ...  

"Normal" commands:

All of the above act like normal commands, and if can take any command:

# grep returns true if it finds something
if grep pattern file ; then ...

Multiple commands:

Or we can use multiple commands. Wrapping a set of commands in ( ... ) runs them in subshell, creating a temporary copy of the shell's state (working directory, variables). If we need to run some program temporarily in another directory:

# this will move to $somedir only for the duration of the subshell 
if ( cd $somedir ; some_test ) ; then ...

# while here, the rest of the script will see the new working
# directory, even after the test
if cd $somedir ; some_test ; then ...
| improve this answer | |

Grouping Commands

Bash provides two ways to group a list of commands to be executed as a unit.

( list ) Placing a list of commands between parentheses causes a subshell environment to be created , and each of the commands in list to be executed in that subshell. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.

$ a=1; (a=2; echo "inside: a=$a"); echo "outside: a=$a"
inside: a=2
outside: a=1

{ list; } Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. The semicolon (or newline) following list is required. Source

${} Parameter expansion Ex:  ANIMAL=duck; echo One $ANIMAL, two ${ANIMAL}s
$() Command substitution Ex: result=$(COMMAND) 
$(()) Arithmetic expansion Ex: var=$(( 20 + 5 )) 

Conditional Constructs

Single Bracket i.e. []
For comparison ==, !=, <, and > and should be used and for numeric comparison eq, ne,lt and gt should be used.

Enhanced Brackets i.e. [[]]

In all the above examples, we used only single brackets to enclose the conditional expression, but bash allows double brackets which serves as an enhanced version of the single-bracket syntax.

For comparison ==, !=, <, and > can use literally.

  • [ is a synonym for test command. Even if it is built in to the shell it creates a new process.
  • [[ is a new improved version of it, which is a keyword, not a program.
  • [[ is understood by Korn and Bash.


| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.