What is the difference between the Bash operators [[ vs [ vs ( vs ((?

223,820

Solution 1

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.


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

Solution 2

  • (…) 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 …
    

Solution 3

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

Solution 4

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.

Solution 5

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 ...
Share:
223,820

Related videos on Youtube

RetroCode
Author by

RetroCode

Updated on September 18, 2022

Comments

  • RetroCode
    RetroCode almost 2 years

    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)
    
    • G-Man Says 'Reinstate Monica'
      G-Man Says 'Reinstate Monica' almost 8 years
    • ilkkachu
      ilkkachu almost 8 years
      Parenthesis and brackets aren't that easy to search for in documentation, and that's all you have if you don't know the names of those features.
  • ilkkachu
    ilkkachu almost 8 years
    Though test and [ are of course builtin commands in Bash, but it's likely that an external binary exists too.
  • Random832
    Random832 almost 8 years
    The external binary for [ is not a symbolic link to test on most modern systems.
  • Alexis Wilke
    Alexis Wilke almost 8 years
    @Random832 Ah... Interest, not only [ is not a symbolic link, it's not the same size as test! So it is its own beast altogether.
  • ilkkachu
    ilkkachu almost 8 years
    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.
  • jlliagre
    jlliagre almost 8 years
    @Random832 The difference is [ needs to look for a closing ] while test does not. Of course a purposely designed single binary can handle both behaviors but my understanding is POSIX doesn't require [ to exist as a file. Its name is not even in the portable file name character set.
  • Alexis Wilke
    Alexis Wilke almost 8 years
    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 ]...
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 8 years
    @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 ./).
  • Random832
    Random832 almost 8 years
    @jlliagre It does require it to exist as a file (hell, it requires cd to exist as a file); the purpose of the portable filename character set doesn't preclude the standard requiring a specific file with a specific name containing a character set outside it. Anyway, modern systems tend to lean away from the "single binary handling different behaviors depending on argv[0] concept - There's a rationale for why it's a considered bad (and why gnu coreutils doesn't do it, not only for [/test, but also ls/dir/vdir) EDIT: gnu.org/prep/standards/html_node/User-Interfaces.html
  • jlliagre
    jlliagre almost 8 years
    @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
    jlliagre almost 8 years
    @Random832 I know quite well that paragraph ( see unix.stackexchange.com/questions/38808/why-is-cd-not-a-progr‌​am/… ) but IMHO there is nothing in this case Solaris is violating. The test utility is definitely provided as a standalone executable. Nowhere is written it requires its variant to be that way, and the fact it doesn't conform with the first and second guidelines stated here pubs.opengroup.org/onlinepubs/9699919799/basedefs/… likely doesn't help supporting the idea.
  • chicks
    chicks almost 8 years
    Thanks for including the 5th option. That's key to understanding how this actually works and is surprisingly underutilized.
  • Julien R.
    Julien R. almost 8 years
    Note that [ is actually a binary, not a internal command or symbol. Generally lives in /bin.
  • SiBrit
    SiBrit almost 8 years
    @JulienR. actually [ is a built in, as is test. There are binary versions available for compatibility reasons. Check out help [ and help test.
  • Stéphane Chazelas
    Stéphane Chazelas over 6 years
    Note that it's [[ -L $file && -f $file ]] (no -a with the [[...]] variant).
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy over 6 years
    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
  • Alexander
    Alexander over 6 years
    (exit 1) has an effect outside of the parentheses.
  • Alexis Wilke
    Alexis Wilke over 6 years
    @Alexander, I applied a correction. Thank you for pointing that out.
  • AnthonyD973
    AnthonyD973 over 6 years
    How to have fun printing new lines: if echo; then echo; fi.
  • Wiimm
    Wiimm over 5 years
    It is not compatible across shell interpreters. If ou write a bash script, [[...]] and ((...)) are compatible for all systems. And in many guides, [[...]] is favored over [...].
  • Jonathan Komar
    Jonathan Komar about 5 years
    See man test if you tried man [ and got lost. That will explain the POSIX variant.
  • Anthony Gatlin
    Anthony Gatlin over 4 years
    I wish I could up-vote this answer more than once. Perfect explanation.
  • Cameron Hudson
    Cameron Hudson over 4 years
    Why would I use if [ condition ] rather than if condition?
  • John1024
    John1024 over 4 years
    @CameronHudson If condition is a valid test condition, such as "$n" -gt 2, then use if [ condition ]. If, instead of a condition, you have a command, then use if command.
  • EAmez
    EAmez over 4 years
    @John1024 do you mean this? => when the condition to be evaluated is a command, use if command. When the condition is something to be evaluated as true or false, use if [command].
  • John1024
    John1024 over 4 years
    @EAmez I was distinguishing between a command, such as grep -q Error logfile, and a valid test condition, such as "$n" -gt 2. The shell uses if command but if [ condition ].
  • Helin Wang
    Helin Wang over 3 years
    Thanks. For if [ ... ], [ is test, then what is ]?
  • javaamtho
    javaamtho over 3 years
    Correction: [ does not parse parameters until it finds a parameter ]; instead, it checks that its last argument is "]", and then ignores it while parsing the rest of its arguments as a test expression. This means that "]" can occur in the middle of the expression as well as the end. For example, if [ "$foo" = ] ]; then will test whether the variable foo is set to "]" (as will if [ ] = "$foo" ]; then).
  • Alexis Wilke
    Alexis Wilke over 3 years
    @GordonDavisson, indeed. I made an edit accordingly.
  • Ciro Santilli Путлер Капут 六四事
    Ciro Santilli Путлер Капут 六四事 over 3 years
    @GordonDavisson thanks, I didn't know that, fixed.
  • John1024
    John1024 over 3 years
    @HelinWang The ] is sometimes called syntactic sugar. It's purpose is to make the [ ... ] expression "easier to read or to express." In other words, when invoked as [, test requires that the last argument to the command must be ]. The reason is that a final ] makes the command look 'nicer.'
  • tgm1024--Monica was mistreated
    tgm1024--Monica was mistreated over 3 years
    It seems to me that using [[ (instead of [) is well worth losing portability in most cases. Anything to avoid excess escaping increases shell readability, and hence reduces bugs. Shell code is already so muddled up that there are routinely errors centered around whether or not something needs one backslash or two, and anything that mitigates the likelihood of saying "WTF just happened here" is a win.
  • Ciro Santilli Путлер Капут 六四事
    Ciro Santilli Путлер Капут 六四事 over 3 years
    @tgm1024--Monicawasmistreated yes, that is also a valid consideration.
  • smwikipedia
    smwikipedia almost 3 years
    Great answer! It should be part of the official document. The Bash if semantic is so different from those in other usual programming languages.
  • jfernandz
    jfernandz over 2 years
    So impressive ...
  • dc46and2
    dc46and2 about 2 years
    Thanks for the great summary of the differences (although I disagree with the recommendation.) Another important difference: the arguments to binary operators such as -eq are evaluated as arithmetic expressions when used with [[. E.g. try [[ 2 -eq "1 + 1" ]] or [[ 10 -eq 0xA ]].