Introduction
Recently, I had an opportunity to write some shell scripts at work.
Since I didn’t have much experience with shell scripting, I looked things up online while writing the code. That’s when I ran into this question:
What’s the difference between writing
if [ ]; thenandif [[ ]]; then?
In this article, I’ll share what I learned about the differences between the two and how to choose which one to use.
The Basic Difference Between [ ] and [[ ]]
| Syntax | Type | Characteristics |
|---|---|---|
if [ condition ]; then |
POSIX-standard | Works on any UNIX environment. The classic format. |
if [[ condition ]]; then |
Bash extension | Bash-only. Safer and more powerful. |
The POSIX-standard syntax is defined so that it behaves the same across all UNIX-like systems.
It works on Linux, macOS, BSD, and any POSIX-compliant shell.
On the other hand, the Bash extension syntax works on Bash and Zsh, but does not work on /bin/sh or other strictly POSIX-compliant shells.
[ ] internally calls the test command.
So this:
if [ -f file ]; then
echo "Exists"
fi
is actually equivalent to:
if test -f file; then
echo "Exists"
fi
Meanwhile, [[ ]] is parsed directly by the shell, which allows safer handling of complex expressions and pattern matching.
Common Conditional Expressions Compared
Here are common examples written using both [ ] and [[ ]].
1. Checking File or Directory Existence
| Condition | Meaning | [ ] |
[[ ]] |
|---|---|---|---|
| File exists | -f | [ -f "$file" ] |
[[ -f $file ]] |
| Directory exists | -d | [ -d "$dir" ] |
[[ -d $dir ]] |
| Does not exist | ! | [ ! -f "$file" ] |
[[ ! -f $file ]] |
Both work the same, but with [[ ]] you can omit quotes safely.
2. String Comparison
| Condition | Meaning | [ ] |
[[ ]] |
|---|---|---|---|
| Equal | = |
[ "$a" = "$b" ] |
[[ $a = $b ]] |
| Not equal | != |
[ "$a" != "$b" ] |
[[ $a != $b ]] |
| Empty | -z |
[ -z "$a" ] |
[[ -z $a ]] |
| Not empty | -n |
[ -n "$a" ] |
[[ -n $a ]] |
-
[ ]→ Always quote variables -
[[ ]]→ Quotes optional and safer
3. Numeric Comparison
| Condition | Meaning | [ ] |
[[ ]] |
|---|---|---|---|
| Equal | -eq | [ "$x" -eq "$y" ] |
[[ $x -eq $y ]] |
| Greater | -gt | [ "$x" -gt "$y" ] |
[[ $x -gt $y ]] |
| Smaller | -lt | [ "$x" -lt "$y" ] |
[[ $x -lt $y ]] |
Same syntax in both cases.
4. AND / OR Conditions
[ ] syntax
[ -f "$a" ] && [ -r "$a" ] # AND(both true)
[ -f "$a" ] || [ -f "$b" ] # OR(one true)
[[ ]] syntax
[[ -f $a && -r $a ]] # AND(both true)
[[ -f $a || -f $b ]] # OR(one true)
[[ ]] allows combining conditions inside a single expression.
5. Pattern Matching
file="report.txt"
# Works in [[ ]]
if [[ $file == *.txt ]]; then
echo "Text file"
fi
# Does NOT work in [ ] (always false)
if [ $file == *.txt ]; then
echo "Will not run"
fi
Wildcards (*, ?) are supported only in [[ ]].
How to Choose
| Use Case | Recommended Syntax |
|---|---|
Needs POSIX compatibility (/bin/sh) |
[ ] |
| Bash-only script, want safety | [[ ]] |
| Avoiding quote-related bugs | [[ ]] |
| Needs compatibility with other shells | [ ] |
Pick One Style and Stick to It
You can use either style, but it’s best to choose one and stay consistent.
Here’s why:
1. Better readability and maintainability
Mixing [ ] and [[ ]] in one script makes readers wonder:
“Why is this one using
[ ]? Is there a special reason?”
Unintended inconsistencies increase mental load and confusion.
2. Prevent incompatibility across shells
[[ ]] works only in Bash.
It fails in /bin/sh:
#!/bin/sh
if [[ -f file ]]; then
echo "Exists"
fi
- Works in bash
- Errors in sh
Therefore:
- Writing a Bash script? → Use
[[ ]] - Want POSIX portability? → Use
[ ]
3. Avoid subtle bugs
Since [ ] and [[ ]] evaluate expressions differently, mixing them can cause unexpected behavior.
Example:
var="*.txt"
# [ ] → filename expansion occurs
if [ $var = "*.txt" ]; then
echo "match"
fi
# [[ ]] → treated as a pattern
if [[ $var == *.txt ]]; then
echo "match"
fi
Even though the lines look similar, their behavior differs.
Consistency prevents surprises.
Summary
-
[ ]is the POSIX-standard test command -
[[ ]]is a Bash extension: safer and more flexible - Both support
-n/-z, but[[ ]]is safer without quotes -
[[ ]]supports combining conditions and pattern matching - Either is fine—but don’t mix them
Closing Thoughts
Before writing this article, I didn’t even know there were two different syntaxes.
I was copying conditional expressions from the internet and ended up mixing [ ] and [[ ]] in the same script without realizing it.
If this article helps even one person avoid that mistake, I’ll be happy.
Be mindful when choosing which syntax to use!
💬 How about you?
- What syntax do you usually use in your shell scripts —
[ ]or[[ ]]? And why? - Have you ever run into unexpected behavior because of differences between
[ ]and[[ ]]?
Top comments (2)
Clear explanation of [ ] vs [[ ]] and when to choose each—really helps avoid subtle shell scripting bugs.
Really clear explanation, I didn’t even know there were two different syntaxes before this.
This breakdown made it click. Especially the part about pattern matching and quote safety, that was new to me.
Thanks for writing this!