What is the difference between the Bash operators [[ vs [ vs ( vs ((?
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
:
-
if [ condition ]
[
is another name for the traditionaltest
command.[
/test
is a standard POSIX utility. All POSIX shells have it builtin (though that's not required by POSIX²). Thetest
command sets an exit code and theif
statement acts accordingly. Typical tests are whether a file exists or one number is equal to another. -
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 theif
statement acts accordingly. Among its extended features, it can test whether a string matches a wildcard pattern (not in busybox sh). -
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. -
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
ifcommand
required variable assignments or other changes to the shell's environment. Such changes do not remain after the subshell completes. -
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).
- With a leading dollar sign,
-
{ … }
braces are like parentheses in that they group commands, but they only influence parsing, not grouping. The programx=2; { x=4; }; echo $x
prints 4, whereasx=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. Theksh93
shell also supports${ cmd;}
as form of command substitution that doesn't spawn a subshell.
- With a leading dollar sign,
-
((…))
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.
- The same syntax is used in arithmetic expressions
-
[ … ]
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), and a space or a character like[ "$x"="$y" ]
;
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 stringhello
: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:
-
[
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 makesX
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: https://stackoverflow.com/questions/21294867/how-to-test-strings-for-lexicographic-less-than-or-equal-in-bash/52707989#52707989
-
-
&&
and||
-
[[ a = a && b = b ]]
: true, logical and -
[ a = a && b = b ]
: syntax error,&&
parsed as an AND command separatorcmd1 && 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 ofa
orb
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 withBASH_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 ...
Related videos on Youtube
RetroCode
Updated on September 18, 2022Comments
-
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' almost 8 years
-
ilkkachu almost 8 yearsParenthesis 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 almost 8 yearsThough
test
and[
are of course builtin commands in Bash, but it's likely that an external binary exists too. -
Random832 almost 8 yearsThe external binary for
[
is not a symbolic link totest
on most modern systems. -
Alexis Wilke almost 8 years@Random832 Ah... Interest, not only
[
is not a symbolic link, it's not the same size astest
! So it is its own beast altogether. -
ilkkachu almost 8 yearsSomehow, 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 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 almost 8 yearsNote 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' 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 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 onargv[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 alsols
/dir
/vdir
) EDIT: gnu.org/prep/standards/html_node/User-Interfaces.html -
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 almost 8 years@Random832 I know quite well that paragraph ( see unix.stackexchange.com/questions/38808/why-is-cd-not-a-program/… ) 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 almost 8 yearsThanks for including the 5th option. That's key to understanding how this actually works and is surprisingly underutilized.
-
Julien R. almost 8 yearsNote that
[
is actually a binary, not a internal command or symbol. Generally lives in/bin
. -
SiBrit almost 8 years@JulienR. actually
[
is a built in, as istest
. There are binary versions available for compatibility reasons. Check outhelp [
andhelp test
. -
Stéphane Chazelas over 6 yearsNote that it's
[[ -L $file && -f $file ]]
(no-a
with the[[...]]
variant). -
Sergiy Kolodyazhnyy over 6 yearsWorth 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 over 6 years(exit 1) has an effect outside of the parentheses.
-
Alexis Wilke over 6 years@Alexander, I applied a correction. Thank you for pointing that out.
-
AnthonyD973 over 6 yearsHow to have fun printing new lines:
if echo; then echo; fi
. -
Wiimm over 5 yearsIt 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 about 5 yearsSee
man test
if you triedman [
and got lost. That will explain the POSIX variant. -
Anthony Gatlin over 4 yearsI wish I could up-vote this answer more than once. Perfect explanation.
-
Cameron Hudson over 4 yearsWhy would I use
if [ condition ]
rather thanif condition
? -
John1024 over 4 years@CameronHudson If
condition
is a validtest
condition, such as"$n" -gt 2
, then useif [ condition ]
. If, instead of acondition
, you have acommand
, then useif command
. -
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, useif [command]
. -
John1024 over 4 years@EAmez I was distinguishing between a command, such as
grep -q Error logfile
, and a validtest
condition, such as"$n" -gt 2
. The shell usesif command
butif [ condition ]
. -
Helin Wang over 3 yearsThanks. For
if [ ... ]
,[
istest
, then what is]
? -
javaamtho over 3 yearsCorrection:
[
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 variablefoo
is set to "]" (as willif [ ] = "$foo" ]; then
). -
Alexis Wilke over 3 years@GordonDavisson, indeed. I made an edit accordingly.
-
Ciro Santilli Путлер Капут 六四事 over 3 years@GordonDavisson thanks, I didn't know that, fixed.
-
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 over 3 yearsIt 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 Путлер Капут 六四事 over 3 years@tgm1024--Monicawasmistreated yes, that is also a valid consideration.
-
smwikipedia almost 3 yearsGreat answer! It should be part of the official document. The Bash
if
semantic is so different from those in other usual programming languages. -
jfernandz over 2 yearsSo impressive ...
-
dc46and2 about 2 yearsThanks 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 ]]
.