1. Overview

While comparing variables in Bash, we generally use single brackets ([ ]) and double brackets ([[ ]]) interchangeably.

For example, we can use the expressions [ 3 -eq 3 ] or [[ 3 -eq 3 ]] while checking whether 3 is equal to 3. Both will be successful in performing the comparison. So, what’s the difference between them?

In this tutorial, we’ll discuss the differences between single and double brackets in Bash.

2. Main Differences

In this section, we’ll briefly discuss the main differences between single and double brackets.

2.1. Single Brackets

[ is a shell built-in command that has always been available in Unix and Linux to evaluate expressions. It still exists for backward compatibility and POSIX compliance.

Let’s verify that *[* is a shell built-in command using the type command:

$ type [
[ is a shell builtin

*[* is an alternative command for the test built-in command. We can use them interchangeably:

$ [ 3 -eq 3 ] && echo “Numbers are equal”
Numbers are equal
$ test 3 -eq 3 && echo “Numbers are equal”
Numbers are equal

The only difference between [ and test is that we must use the closing ] for surrounding the comparison.

2.2. Double Brackets

The double brackets, [[ ]], were introduced in the Korn Shell as an enhancement that makes it easier to use in tests in shell scripts. We can think of it as a convenient alternative to single brackets.

It’s available in many shells like Bash and zsh. However, double brackets aren’t POSIX compliant.

[[ is a shell keyword. Let’s check it using the type command:

$ type [[
[[ is a shell keyword

3. Other Differences

In this section, we’ll discuss the other differences between single and double brackets.

3.1. Comparison Operators

It’s possible to use the comparison operators with the double brackets. Let’s use the less than operator (<) for string comparison:

$ [[ 1 < 2 ]] && echo “1 is less than 2”
1 is less than 2

Here, we checked whether 1 is less than 2 using the less than operator. The comparison was successful. However, using single brackets instead of double brackets will give a syntax error:

$ [ 1 < 2 ] && echo “1 is less than 2”
bash: 2: No such file or directory

*In this case, Bash treated the < operator as a file redirection operator. Therefore, we must use the escape character (\*) before the < operator for a successful comparison within single brackets:

$ [ 1 \< 2 ] && echo “1 is less than 2”
1 is less than 2

Now, the comparison using single brackets was successful.

*Similarly, we must use the escape character before the greater than operator (>) for string comparison within single brackets.*

The usage of the integer comparison operators such as -eq, -ne, -gt, -lt, -ge, and -le is the same for both.

3.2. Boolean Operators

We must use the && operator for the logical AND operation and the || operator for the logical OR operation while using the double brackets.

Let’s check that 3 is equal to 3, and 4 is equal to 4:

$ [[ 3 -eq 3 && 4 -eq 4 ]] && echo “Numbers are equal”
Numbers are equal

However, we must use the -o and -a test operators for the logical OR and logical AND operations, respectively, while using single brackets.

Let’s repeat the last comparison we performed using single brackets this time:

$ [ 3 -eq 3 -a 4 -eq 4 ] && echo “Numbers are equal”
Numbers are equal

The comparison is again successful. But we used -a instead of && because of single brackets.

3.3. Grouping Expressions

We can group expressions within the double brackets using parentheses. One reason for grouping may be reading the expressions more easily:

$ [[ 3 -eq 3 && (2 -eq 2 && 1 -eq 1) ]] && echo “Parentheses can be used”
Parentheses can be used

Here, we grouped the expression, 2 -eq 2 && 1 -eq 1, using the parentheses. Then, we used the grouped expression, (2 -eq 2 && 1 -eq 1), as the second expression of && within the double brackets. The first expression of && was 3 -eq 3. The grouping was successful.

Now, let’s use the same expression using single brackets:

$ [ 3 -eq 3 -a (2 -eq 2 -a 1 -eq 1) ] && echo “Parentheses can be used”
bash: syntax error near unexpected token ‘(‘

We used -a instead of && because of single brackets. However, we got a syntax error.

We must use the escape character before the opening and closing parentheses for a successful grouping. There must also be a space after and before the opening and closing parentheses:

$ [ 3 -eq 3 -a \( 2 -eq 2 -a 1 -eq 1 \) ] && echo “Parentheses can be used”
Parentheses can be used

Grouping the expressions using the parentheses within single brackets was successful in this case.

3.4. Pattern Matching

It’s possible to use pattern matching with the double brackets. For example, we can use the wildcard * (asterisk) within the double brackets:

$ name=”Alice”
$ [[ $name = *c* ]] && echo “Name includes c”
Name includes c
$ echo $?
0

Here, we first assigned the value Alice to the shell variable name using the statement name=”Alice”. Then, we checked whether the variable name includes the letter c using [[ $name = *c* ]]. * means zero or more characters. The result was successful, as expected.

However, it isn’t possible to use pattern matching within single brackets:

$ name=”Alice”
$ [ $name = *c* ] && echo “Name includes c”
$ echo $?
1

In this case, the only difference between the previous example is the single brackets. We replaced the double brackets in the previous example with the single brackets. There weren’t any syntax errors, but pattern matching was unsuccessful as the exit status of [ $name = *c* ] && echo “Name includes c” was 1.

3.5. Regular Expressions

It’s also possible to use regular expressions within the double brackets:

$ name=”Alice”
$ [[ $name =~ ^Ali ]] && echo ”Regular expressions can be used”
Regular expressions can be used

We first assigned the value Alice to the shell variable name using the statement name=”Alice”. Then, we checked whether the variable name starts with Ali using [[ $name =~ ^Ali ]]. The =~ built-in operator is used for matching the regular expressions. The caret (^) matches the beginning of a word. Hence ^Ali means the words beginning with Ali. As it’s apparent from the output, we could detect that the value of the shell variable starts with Ali.

Let’s try the same regular expression pattern using single brackets:

$ name=”Alice”
$ [ $name =~ ^Ali ] && echo ”Regular expressions can be used”
bash: [: =~: binary operator expected

Now, we got an error. So, it isn’t possible to use regular expressions within single brackets.

3.6. Word Splitting

Bash doesn’t perform word splitting inside double brackets. For example, if a variable has a string value containing spaces, Bash doesn’t split the string into words:

$ filename=”nonexistent file”
$ [[ ! -e $filename ]] && echo ”File doesn’t exist”
File doesn’t exist

Here, we checked the existence of the file nonexistent file. The filename includes a space. We could directly use the variable, filename, within the double brackets as the double brackets didn’t split the filename.

Let’s try it with single brackets:

$ filename=”nonexistent file”
$ [ ! -e $filename ] && echo ”File doesn’t exist”
bash: [: nonexistent: binary operator expected

We got an error in this case since the filename nonexistent file was split into two words, nonexistent and file. This is related to the IFS variable. If IFS isn’t set, the shell splits the string when it encounters a space, tab, or newline.

We must put the variable in double quotes if we want to prevent the word splitting within single brackets:

$ filename=”nonexistent file”
$ [ ! -e “$filename” ] && echo ”File doesn’t exist”
File doesn’t exist

Now, checking the existence of a filename containing spaces was successful within single brackets.

4. Conclusion

In this article, we discussed the differences between single and double brackets in Bash.

The single bracket is a built-in command that’s older than the double bracket. The double bracket is an enhanced version of the single bracket. If we want the portability of scripts, we should prefer single brackets. However, using the double brackets is generally more convenient.

We saw that we could use comparison operators and boolean operators in both, apart from a few differences. The same thing is valid for grouping expressions and word splitting. However, using pattern matching and regular expressions is only possible in the double brackets.