568

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)
4

7 Answers 7

642

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

if
   command-list1
then
   command-list2
else
   command-list3
fi

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.

Note that [ ... ] and [[ ... ]] require whitespace around them, while (...) and ((...)) do not.


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

15
  • 72
    Thanks for including the 5th option. That's key to understanding how this actually works and is surprisingly underutilized.
    – chicks
    Commented Aug 30, 2016 at 19:32
  • 13
    Note that [ is actually a binary, not a internal command or symbol. Generally lives in /bin.
    – Julien R.
    Commented Sep 3, 2016 at 12:17
  • 21
    @JulienR. actually [ is a built in, as is test. There are binary versions available for compatibility reasons. Check out help [ and help test.
    – OldTimer
    Commented Sep 20, 2016 at 4:23
  • 22
    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 Commented Nov 30, 2017 at 18:10
  • 9
    I wish I could up-vote this answer more than once. Perfect explanation. Commented Oct 9, 2019 at 11:33
160
  • (…) 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
    else
      echo >&2 "Fatal error. This script requires mydir to exist."
    fi
    
  • 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 …
    
5
  • 1
    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 ]... Commented Aug 27, 2016 at 23:34
  • 9
    @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 ./). Commented Aug 27, 2016 at 23:38
  • Note that it's [[ -L $file && -f $file ]] (no -a with the [[...]] variant). Commented Nov 30, 2017 at 15:25
  • The part about braces being keywords is important. It's what allows you to do things like: { task1 && task2 && task3 && echo "Success!"; } || { echo "One of the tasks failed!"; exit 1; } in a larger script. If you use parentheses, exit 1 will just exit out of a subshell and any surrounding script will keep running. Without the ending semicolon in each set of braces, you'll get "Unexpected end of file".
    – Jemenake
    Commented May 18, 2023 at 22:54
  • The examples make the better answer.
    – galaxis
    Commented Sep 12, 2023 at 10:48
59

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

8
  • @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. Commented Feb 5, 2019 at 9:11
  • 3
    See man test if you tried man [ and got lost. That will explain the POSIX variant. Commented Jun 4, 2019 at 7:25
  • 2
    Correction: ] is an argument to the [ command, but it doesn't prevent further arguments from being used. ] must be the last argument to [, but it can also occur as part of the test expression. For example, if [ "$foo" = ] ]; then will test whether the variable foo is set to "]" (as will if [ ] = "$foo" ]; then). Commented Jan 5, 2021 at 5:07
  • 1
    @tgm1024--Monicawasmistreated yes, that is also a valid consideration. Commented Jan 13, 2021 at 8:32
  • 1
    So impressive ...
    – jfernandz
    Commented Mar 2, 2022 at 19:00
27

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 knows when it was started as [ and it can ignore its last parameter, which is expected to be ]. 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.

15
  • 1
    Though test and [ are of course builtin commands in Bash, but it's likely that an external binary exists too.
    – ilkkachu
    Commented Aug 27, 2016 at 19:12
  • 1
    The external binary for [ is not a symbolic link to test on most modern systems.
    – Random832
    Commented Aug 27, 2016 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
    Commented Aug 27, 2016 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
    Commented Aug 28, 2016 at 4:00
  • 3
    (exit 1) has an effect outside of the parentheses.
    – Alexander
    Commented Feb 10, 2018 at 8:23
18

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 ...
0
12

Special characters:

Char. Description
Whitespace — this is a tab, newline, vertical tab, form feed, carriage return, or space. Bash uses whitespace to determine where words begin and end. The first word is the command name and additional words become arguments to that command.
$ Expansion — introduces various types of expansion: parameter expansion (e.g. $var or ${var}), command substitution (e.g. $(command)), or arithmetic expansion (e.g. $((expression))).
'' Single quotes — protect the text inside them so that it has a literal meaning. With them, generally any kind of interpretation by Bash is ignored: special characters are passed over and multiple words are prevented from being split.
"" Double quotes — protect the text inside them from being split into multiple words or arguments, yet allow substitutions to occur; the meaning of most other special characters is usually prevented.
\ Escape — (backslash) prevents the next character from being interpreted as a special character. This works outside of quoting, inside double quotes, and generally ignored in single quotes.
# Comment — the # character begins a commentary that extends to the end of the line. Comments are notes of explanation and are not processed by the shell.
= Assignment -- assign a value to a variable (e.g. logdir=/var/log/myprog). Whitespace is not allowed on either side of the = character.
[[ ]] Test — an evaluation of a conditional expression to determine whether it is "true" or "false". Tests are used in Bash to compare strings, check the existence of a file, etc.
! Negate — used to negate or reverse a test or exit status. For example: ! grep text file; exit $?.
>, >>, < Redirection — redirect a command's output or input to a file.
| Pipe — send the output from one command to the input of another command. This is a method of chaining commands together. Example: echo "Hello beautiful." | grep -o beautiful.
; Command separator — used to separate multiple commands that are on the same line.
{ } Inline group — commands inside the curly braces are treated as if they were one command. It is convenient to use these when Bash syntax requires only one command and a function doesn't feel warranted.
( ) Subshell group — similar to the above but where commands within are executed in a subshell (a new process). Used much like a sandbox, if a command causes side effects (like changing variables), it will have no effect on the current shell.
(( )) Arithmetic expression — with an arithmetic expression, characters such as +, -, *, and / are mathematical operators used for calculations. They can be used for variable assignments like (( a = 1 + 4 )) as well as tests like if (( a < b )).
$(( )) Arithmetic expansion — Comparable to the above, but the expression is replaced with the result of its arithmetic evaluation. Example: echo "The average is $(( (a+b)/2 ))".
*, ? Globs -- "wildcard" characters which match parts of filenames (e.g. ls *.txt).
~ Home directory — the tilde is a representation of a home directory. When alone or followed by a /, it means the current user's home directory; otherwise, a username must be specified (e.g. ls ~/Documents; cp ~john/.bashrc .).
& Background -- when used at the end of a command, run the command in the background (do not wait for it to complete).

Deprecated special characters (recognized, but not recommended):

Char. Description
` ` Command substitution - use $( ) instead.
[ ] Test - an alias for the old test command. Commonly used in POSIX shell scripts. Lacks many features of [[ ]].
$[ ] Arithmetic expression - use $(( )) instead.

Source

0

Beware numeric comparisons. Quiz what is the output?

if ((10 > 2)); then echo 1; fi
if [[ 10 > 2 ]]; then echo 2; fi
if [ 10 -gt 2 ]; then echo 3; fi
if [[ 10 -gt 2 ]]; then echo 4; fi

answer: 1 3 4

Putting quotes around the numbers does not make any difference.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .