6

What is the difference between using [[ condition ]] and [ condition ] or (( condition )) and ( condition )? In what scenario do we need to use either of these?

  • (( 10 > 9 )) works but (( 10 -gt 9 )) doesn't
  • [[ 10 -gt 9 ]] works but [[ 10 > 9 ]] doesn't

4 Answers 4

14

((...)) is the shell's arithmetic construct. The operators you can use are documented in the manual: 6.5 Shell Arithmetic

(...) is a grouping construct that executes the contained commands in a subshell: 3.2.4.3 Grouping Commands

[...] is the "legacy" conditional construct. Documentation is at 6.4 Bash Conditional Expressions

[[...]] does everything that [...] does. The difference is that word splitting and glob expansion are not performed for variables inside [[...]] so quoting the variables is not so crucial. Additionally, [[ can do pattern matching with the == operator and regular expression matching with the =~ operator.

The reason [[ 10 > 9 ]] gives you an unexpected result is that the > operator inside [[...]] is for string comparison and the string "10" is "less than" the string "9".

3
  • Thanks glenn . This site is awesome for beginner like me.
    – rizwan
    Mar 20, 2020 at 15:26
  • So this means that both [[ ]] and (( )) can be used as a test condition in IF right ?
    – rizwan
    Mar 20, 2020 at 15:28
  • Yes. But look at the documentation (such as help if at a bash prompt) - ANY command can be used as the condition of an if statement. The branching is based on the exit status of the command. [[ and (( produce an exit status Mar 20, 2020 at 15:43
4

About ((…)) and (…)

As stated in this other answer, ((…)) is the shell's arithmetic construct (a bashism) and (…) runs command(s) in a subshell. They are very different from each other and from [ … ] or [[ … ]].

On the other hand [ … ] and [[ … ]] are very similar in simple cases; they may be confused. The rest of this answer concentrates on [ … ] and [[ … ]].


Formal note

My goals here are:

  • to elaborate on differences and similarities between [ … ] and [[ … ]];
  • to provide a canonical answer in the subject of [ … ] vs [[ … ]], so questions that can be answered by "[ is not [[" can be linked here;
  • to provide an answer exhaustive enough, so users are no longer tempted to post answers that focus on a single difference each.

What is [?

  1. [ performs a test.

    The purpose of [ is to test something (e.g. if some file exists) and to return exit status zero if the test succeeds, non-zero otherwise.

  2. [ is a command.

    [ is a command name like cat or echo. [ (like echo) is a builtin in Bash, but this only means it can be run without spawning an additional process, it still behaves like a command. There is probably a standalone [ executable in your system (e.g. /bin/[); try type -a [ in Bash to find out.

  3. [ requires a space after.

    As a command, [ takes arguments. Like with any other command, arguments must be separated one from another and from the command name. [foo is a different command name, it is not equivalent to [ foo (similarly and more obviously echoHello world is not echo Hello world). You certainly need a space (or a tab) after [.

  4. [ requires ].

    [ requires ] as the last argument, otherwise it refuses to work. This is only to make [ … ] stand out as a block, as if it was some special syntax; but it's not a special syntax. To pass ] as the last argument you need a space (or a tab) before it. For comparison: the last argument in [ … bar] is bar] which is not ], so [ … bar] is not a valid command.

  5. [ is (almost) like test.

    [ … ] is equivalent to test …. In other words you can convert any [ command to a test command by removing the ] and changing the command name. And you can convert any test command to a [ command by changing the command name and adding ].

  6. [ is not special.

    It's good to remember [ is not special, it's only a somewhat fancy name for a command. If you acknowledge this then you won't be surprised by the fact that all the parsing and processing the shell does before running a command (test or echo, ls etc.) also happens if the command is [. In particular:

    • [ is not aware if you quoted any of its arguments, it gets arguments after the shell removes quotes.

    • Unquoted and unescaped && or || between [ and ] is not interpreted as an argument to [. In other words [ … || … ] is interpreted as [ … (invalid because there is no ]) and … ] (a separate command, whatever is) logically connected with ||, exactly like command1 || command2.

  7. [ is specified by POSIX.

    There is the POSIX specification for [/test. You can call [ portable. Notes:

    • Some primaries are extensions, some are marked as obsolescent. E.g. our invalid code ([ … || … ]) can be fixed as [ … -o … ], but since -o is obsolescent, a better fix is [ … ] || [ … ].
    • Implementations may support more primaries. The [ builtin in Bash does (see help test); the [ binary (like /bin/[) in your OS may (see man test).

What is [[? How is [[ different than [?

Notes:
- The question is tagged , now we're talking about [[ in Bash.
- The numbered paragraphs in this section correspond to the numbered paragraphs in the previous section.

  1. (similarity) [[ performs a test.

    The purpose of [[ is to test something (e.g. if some file exists) and to return exit status zero if the test succeeds, non-zero otherwise. [[ can test anything [ can, and more.​

  2. (difference) [[ is a keyword.

    [[ is a shell keyword (invoke type [[ in Bash to confirm this). Like the [ builtin, the [[ keyword belongs to Bash; but it does not behave like a command.

  3. (similarity) [[ requires a space after.

    (Or a tab).

  4. (similarity) [[ requires ]].

    [[ requires ]] later in the code, however it's not only to make [[ … ]] stand out as a block. It is a special syntax (a difference, we will get to it). You need a space (or a tab) before ]].

  5. (difference) There is no ordinarily-looking command equivalent to [[.

    There is no other command/keyword/whatever that can replace [[ in the way test can replace [.

  6. (difference) [[ is special.

    [[ changes the way the shell parses and interprets the code, up to the matching ]]. Normal parsing and processing the shell does before running [ or test (or echo, ls etc.) does not apply inside [[ … ]]. In particular:

    • [[ is aware if you quoted any of its "arguments". Double-quoting variables is not necessary (while normally it is), but in some cases double-quoting a string (or a variable) makes a difference (see this answer where it mentions "the right-hand side of = or == or != or =~").

    • && or || between [[ and ]] belongs to the [[ … ]] construct. [[ … || … ]] can be a valid piece of code, virtually equivalent to [[ … ]] || [[ … ]].

  7. (difference) [[ is not specified by POSIX.

    [[ is not portable. [[ works in Bash, it doesn't work in pure sh. Other shells may support [[ (e.g. Zsh does) but their [[ may be different than the [[ of Bash.

    [[ is not designed to comply with the specification of [/test. In many cases replacing [ with [[ and ] with ]] will give you a [[ … ]] snippet equivalent to the original [ … ] snippet; but not always, sometimes the inside needs to be adjusted. So don't change [ to [[ blindly. Blindly changing [[ to [ is even more risky because there are tests [[ can do while [ cannot.


Other notes:

  1. Neither [ nor [[ is a part of the if … then … syntax. Remember if just tests the exit status of some code. if true; then … is valid, if sleep 5; then … is valid, similarly if [ … ]; then … or if [[ … ]]; then … is valid (if the [ … ] or [[ … ]] part is itself a valid snippet).

    Since ((…)) or (…) also returns some exit status (that depends on what's inside), it may be used after if as well.

  2. Personally I prefer using [ for any test [ can easily do, resorting to [[ when really necessary. This way I don't get used to non-portable [[ and I know what I can do in a shell not as rich as Bash.

  3. The [ executable in your OS may not be strictly equivalent to the [ builtin in your Bash. If [ is invoked by Bash then the builtin will do the job. If [ is invoked by something else, then the executable will do the job. E.g. find … -exec [ … ] … uses the executable. There is no standard [[ executable (at least in the systems I know), so find … -exec [[ … ]] … will never work. If there was a [[ executable then it would have to behave like a command, it wouldn't be able to mimic the keyword.

1

I would have put this in a comment, but am not yet allowed to.

One difference I have noticed between [] and [[]], is that in the former it is possible to use multiple comparisons.

The same comparison throws a syntax error in [[]]

a=1
b=2

This works:

$  [ "$a" -gt 0 -a "$b" -gt "$a" ] && { echo '[b>a>0]'; }
[b>a>0]

This does not work:

$ [[ $a -gt 0 -a $b -gt $a ]] && { echo '[[b>a>0]]'; }
-bash: syntax error in conditional expression
-bash: syntax error near `-a'

I haven't really dug in to see why that is so, but when using multiple comparisons, I use [].

3
  • -1. Feedback: This answer only addresses a small part of the question. Worse, its premise is false. One can do [[ $a -gt 0 && $b -gt $a ]] and it will work. There is no functional difference in this aspect, only syntactical (-a vs &&). I'm not particularly happy downvoting your try to share knowledge, please don't take this personally. If there really was a functional difference you described, then I wouldn't vote down, even though the answer is limited in scope. Aug 13, 2021 at 5:51
  • Thanks, I seem to forget about && within a test. It may be that it does work this way, but the premise I presented isn't false. The test that works in [] does not work in [[]], and so, they are not exactly the same. Aug 14, 2021 at 6:37
  • IMO this syntactical difference is not worth an answer. One tool uses -a, another uses &&. One tool uses ], another uses ]]. Is the latter difference substantial? Why is the former substantial? The main point of your answer is not "-a vs &&". The main point is literally "one difference is in the former it is possible to use multiple comparisons", implying it's impossible in the other tool (otherwise it wouldn't be a difference). But it is possible to use multiple comparisons in any of them. That's what makes the answer misleading. Aug 14, 2021 at 6:43
1

There is a huge difference between [ ] and [[ ]]:

a=0;
if [ $a = 1 -a $(grep ERR 1.log|wc -l) -ne 0 ];then
      echo "error";
fi 

when using [ ], the and operator -a does not support Short-circuit evaluation. In the above code, even the first condition returns false, the second condition is still be evaluated. If the file 1.log does not exist, it will complain "No such file or directory".

But for [[ ]]:

a=0
if [[ $a = 1 && $(grep ERR 1.log|wc -l) -ne 0 ];then
    echo "error"
fi

even the file "1.log" does not exist, it will be OK because the second condition will not be evaluated since the first condition is false.

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.