DEV Community

ak0047
ak0047

Posted on

What’s the Difference Between [ ] and [[ ]] in Shell Scripts?

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 [ ]; then and if [[ ]]; 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
Enter fullscreen mode Exit fullscreen mode

is actually equivalent to:

if test -f file; then
    echo "Exists"
fi
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

[[ ]] syntax

[[ -f $a && -r $a ]]    # AND(both true)
[[ -f $a || -f $b ]]    # OR(one true)
Enter fullscreen mode Exit fullscreen mode

[[ ]] 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jjbb profile image
Jason Burkes

Clear explanation of [ ] vs [[ ]] and when to choose each—really helps avoid subtle shell scripting bugs.

Collapse
 
itsugo profile image
Aryan Choudhary

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!