Every useful script eventually needs to make a decision. Should it delete the file or not? Is the disk almost full? Did the user type “yes” or “no”? That’s where conditional statements come in. In this guide, I’ll break down how if, elif, else, and case work in Bash, with plenty of real examples you can copy straight into your own scripts.
What Are Conditional Statements?
A conditional statement lets a script choose between different paths based on whether something is true or false. In Bash, “true” and “false” aren’t quite like in other languages — they’re based on exit status codes. A command that exits with status 0 is considered successful (true), and anything else is considered a failure (false).
The if Statement
Basic Syntax
if [ condition ]
then
command
fi
Example 1: Simple Number Comparison
#!/bin/bash
num=10
if [ $num -gt 5 ]
then
echo "The number is greater than 5"
fi
Output:
The number is greater than 5
Example 2: Using if...else
#!/bin/bash
age=16
if [ $age -ge 18 ]
then
echo "You are an adult"
else
echo "You are a minor"
fi
Output:
You are a minor
Example 3: Using if...elif...else
#!/bin/bash
marks=75
if [ $marks -ge 90 ]
then
echo "Grade: A"
elif [ $marks -ge 75 ]
then
echo "Grade: B"
elif [ $marks -ge 60 ]
then
echo "Grade: C"
else
echo "Grade: F"
fi
Output:
Grade: B
Bash checks each condition top to bottom and runs the first block whose condition is true, skipping the rest.
Comparison Operators
For numbers:
| Operator | Meaning |
|---|---|
-eq | equal to |
-ne | not equal to |
-gt | greater than |
-lt | less than |
-ge | greater than or equal to |
-le | less than or equal to |
For strings:
| Operator | Meaning |
|---|---|
= | equal |
!= | not equal |
-z | string is empty |
-n | string is not empty |
Example: String Comparison
#!/bin/bash
name="admin"
if [ "$name" = "admin" ]
then
echo "Welcome, administrator"
else
echo "Access denied"
fi
Output:
Welcome, administrator
File Test Operators
Bash has built-in checks for files and directories, which I use constantly in setup and deployment scripts:
| Operator | Meaning |
|---|---|
-e | file exists |
-f | is a regular file |
-d | is a directory |
-r | is readable |
-w | is writable |
-x | is executable |
-s | file exists and is not empty |
Example: Checking If a File Exists
#!/bin/bash
file="/etc/passwd"
if [ -f "$file" ]
then
echo "$file exists and is a regular file"
else
echo "$file does not exist"
fi
Output:
/etc/passwd exists and is a regular file
Logical Operators: AND, OR, NOT
You can combine multiple conditions using && (AND), || (OR), and ! (NOT).
#!/bin/bash
age=25
citizen="yes"
if [ $age -ge 18 ] && [ "$citizen" = "yes" ]
then
echo "Eligible to vote"
else
echo "Not eligible to vote"
fi
Output:
Eligible to vote
Using [[ ]] Instead of [ ]
Bash provides an extended test command [[ ]], which is generally safer and more powerful than the POSIX [ ]. It supports pattern matching and doesn’t require quoting variables as strictly.
#!/bin/bash
filename="report.txt"
if [[ $filename == *.txt ]]
then
echo "This is a text file"
fi
Output:
This is a text file
Notice how *.txt works as a pattern inside [[ ]] — that wouldn’t work reliably inside [ ].
The case Statement
When you have many possible values to check against a single variable, case is far cleaner than a long chain of elif statements.
Basic Syntax
case $variable in
pattern1)
command
;;
pattern2)
command
;;
*)
default command
;;
esac
Example: Menu-Style Script
#!/bin/bash
echo "Enter a fruit name:"
read fruit
case $fruit in
apple)
echo "Apples are red or green"
;;
banana)
echo "Bananas are yellow"
;;
grape)
echo "Grapes grow in bunches"
;;
*)
echo "Unknown fruit"
;;
esac
Sample run:
Enter a fruit name:
banana
Bananas are yellow
The * pattern acts as a catch-all default, similar to else.
Example: Handling Command-Line Arguments
#!/bin/bash
case $1 in
start)
echo "Starting the service..."
;;
stop)
echo "Stopping the service..."
;;
restart)
echo "Restarting the service..."
;;
*)
echo "Usage: $0 {start|stop|restart}"
;;
esac
This is exactly the pattern you’ll see in traditional init scripts (/etc/init.d/*).
How This Works Internally
[ condition ]is literally thetestcommand.[is a program (or shell builtin) that evaluates the expression and returns exit status 0 or 1.[[ condition ]]is a Bash keyword, parsed directly by the shell, which is why it supports features like==pattern matching without needing quotes around every variable.ifdoesn’t actually check for “true” or “false” — it checks the exit status of the command that follows it.if some_commandrunssome_commandand takes thethenbranch if its exit code is 0.casecompares the variable against each pattern using shell glob-style matching, not regex, and stops at the first match.
Real-World Use Cases
- Disk space monitoring:
usage=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$usage" -gt 90 ]
then
echo "Warning: Disk usage is above 90%!"
fi
- Validating user input:
read -p "Enter your age: " age
if ! [[ $age =~ ^[0-9]+$ ]]
then
echo "Please enter a valid number"
fi
- Conditional deployment logic:
if [ "$ENVIRONMENT" = "production" ]
then
echo "Deploying to production servers"
else
echo "Deploying to staging servers"
fi
Best Practices
- Always quote variables inside
[ ]to avoid errors when they’re empty or contain spaces. - Prefer
[[ ]]for Bash-specific scripts since it’s more forgiving and supports regex/glob matching. - Use
caseinstead of longelifchains for anything with more than 3-4 branches — it’s more readable. - Keep conditions simple; if a condition gets too complex, break it into a variable with a clear name first.
Security Considerations
- Never pass unsanitized user input directly into a condition that gets
eval‘d. - Be cautious with
[[ $var == $pattern ]]when$patterncomes from user input, since it can unintentionally act as a glob. - Validate numeric input with a regex check (
[[ $var =~ ^[0-9]+$ ]]) before using it in arithmetic comparisons, to avoid script errors or unexpected behavior.
Optimization Tips
- Use
[[ ]]over[ ]where possible — it’s a shell builtin and slightly faster since it avoids forking an externaltestprocess on some systems. - For simple pass/fail checks, you can skip
ifentirely and just use&&/||:command1 && command2runscommand2only ifcommand1succeeds. - Combine related conditions into a single
casestatement instead of multiple sequentialifblocks when checking the same variable.
Troubleshooting Common Issues
- “unary operator expected” error — usually means a variable is empty or unquoted; always quote it:
[ "$var" -eq 5 ]. - Condition never matches — check for extra whitespace or case sensitivity issues in string comparisons.
ifalways executes the “then” branch — rememberifchecks exit status, not literal truthiness; make sure your condition actually returns 0/1 as expected.
Frequently Asked Questions
Q: What’s the difference between [ ] and [[ ]]? A: [ ] is the POSIX-compliant test command; [[ ]] is a Bash-specific keyword with more features like pattern matching and safer variable handling.
Q: Can I use case for numeric ranges? A: Yes, using patterns like [0-9]) or combining with ?(...) extended globs, though for true numeric ranges an if with -lt/-gt is often clearer.
Q: How do I check multiple conditions at once? A: Combine them with && (AND) or || (OR) inside [[ ]], e.g. if [[ $a -gt 5 && $b -lt 10 ]].
Common Mistakes to Avoid
- Forgetting the space inside brackets:
[$x -eq 5]is invalid; it must be[ $x -eq 5 ]. - Using
=for numeric comparison instead of-eq. - Missing the
;;at the end of eachcaseblock. - Not quoting variables that might be empty, causing “unary operator expected” errors.
Summary
Conditional statements are how your Bash scripts make decisions. if/elif/else handles general logic based on exit statuses and comparisons, while case shines when you’re matching one variable against many possible patterns. Understanding that Bash conditions are really about exit codes — not “true/false” in the traditional programming sense — will save you a lot of confusion as your scripts grow more complex.
