Table of Contents
- What Are Conditional Statements?
- Types of Conditional Statements in Bash
- Conditional Expressions and Operators
- The
caseStatement - Practical Examples
- Best Practices
- Conclusion
- References
What Are Conditional Statements?
Conditional statements are control structures that let a script “make choices” by evaluating a condition and executing code based on whether the condition is true (exit status 0) or false (exit status non-zero).
In Bash, conditions are typically checked using test commands (e.g., [ ], [[ ]], or test), which return an exit status indicating the result of the check. For example, [ -f "file.txt" ] returns 0 (true) if file.txt is a regular file, and 1 (false) otherwise.
Types of Conditional Statements in Bash
The if Statement
The simplest conditional is the if statement, which executes a block of code only if a condition is true.
Syntax:
if condition; then
# Code to run if condition is true
fi
condition: A test command (e.g.,[ -f "file.txt" ]).then: Separates the condition from the code block (can also be on the same line asifwith a semicolon:if condition; then).fi: Closes theifblock (reverse ofif).
Example: Check if a file exists:
file="example.txt"
if [ -f "$file" ]; then
echo "$file exists and is a regular file."
fi
The if-else Statement
Use if-else to execute one block when the condition is true and another when it’s false.
Syntax:
if condition; then
# Code if true
else
# Code if false
fi
Example: Check if a file exists; create it if not:
file="example.txt"
if [ -f "$file" ]; then
echo "$file exists."
else
echo "$file does not exist. Creating it..."
touch "$file"
fi
The if-elif-else Statement
For multiple conditions, use if-elif-else (“elif” = “else if”). It checks conditions in order and executes the first true block.
Syntax:
if condition1; then
# Code if condition1 is true
elif condition2; then
# Code if condition2 is true (and condition1 is false)
else
# Code if all conditions are false
fi
Example: Categorize a number:
read -p "Enter a number: " num
if [ "$num" -lt 10 ]; then
echo "Number is less than 10."
elif [ "$num" -lt 20 ]; then
echo "Number is between 10 and 19."
else
echo "Number is 20 or greater."
fi
Nested if Statements
You can nest if statements inside other if, elif, or else blocks to check multiple layers of conditions.
Syntax:
if outer_condition; then
# Outer condition is true
if inner_condition; then
# Inner condition is true
else
# Inner condition is false
fi
else
# Outer condition is false
fi
Example: Check if the user is root, then check if a directory exists:
if [ "$(id -u)" -eq 0 ]; then # Check if user is root (UID 0)
echo "Running as root."
dir="/var/log/myscript"
if [ -d "$dir" ]; then # Nested: check if directory exists
echo "Directory $dir exists."
else
echo "Creating $dir..."
mkdir -p "$dir"
fi
else
echo "Error: This script requires root privileges."
exit 1 # Exit with error code 1
fi
Conditional Expressions and Operators
To evaluate conditions, Bash relies on test commands and operators. Let’s break down the most common tools and how to use them.
Test Command: [ ] vs [[ ]]
Bash provides two primary ways to write conditions:
1. POSIX [ ] (Square Brackets)
The [ ] syntax is a synonym for the test command (e.g., [ -f "file" ] is equivalent to test -f "file"). It is POSIX-compliant, making scripts portable across shells.
Rules for [ ]:
- Always leave spaces between brackets and the condition (e.g.,
[ -f file ]works;[-f file]does not). - Use quotes around variables to avoid word-splitting (e.g.,
[ "$var" = "hello" ]). - Escape special characters (e.g.,
[ "$var" = "a*" ]matches literala*, not a pattern).
2. Bash-Specific [[ ]] (Double Brackets)
[[ ]] is a Bash extension with more features than [ ], including pattern matching, logical operators inside the brackets, and no need to escape special characters. It is not POSIX-compliant, so use it only in Bash scripts.
Advantages of [[ ]]:
- Supports pattern matching with
*and?(e.g.,[[ "$var" == a* ]]checks ifvarstarts with “a”). - Logical operators
&&(and) and||(or) work inside the brackets (no need for-aor-o). - No word-splitting issues (variables don’t always need quotes, but quoting is still good practice).
Example: Pattern Matching with [[ ]]
name="Alice"
if [[ "$name" == A* ]]; then # Checks if name starts with "A"
echo "Name starts with 'A'."
fi
File Operators
File operators check properties of files/directories. Use them with [ ] or [[ ]].
| Operator | Description | Example |
|---|---|---|
-e | File/directory exists | [ -e "file.txt" ] |
-f | Regular file (not a directory/symlink) | [[ -f "data.csv" ]] |
-d | Directory | [ -d "/tmp" ] |
-r | Readable by the current user | [[ -r "secret.txt" ]] |
-w | Writable by the current user | [ -w "output.log" ] |
-x | Executable by the current user | [[ -x "./script.sh" ]] |
-s | File is not empty (size > 0) | [ -s "log.txt" ] |
-L | Symbolic link | [[ -L "link.txt" ]] |
String Operators
String operators compare text values or check string properties.
| Operator | Description | Example (with [ ]) | Example (with [[ ]]) |
|---|---|---|---|
=/== | Strings are equal (use = in [ ]) | [ "$str1" = "$str2" ] | [[ "$str1" == "$str2" ]] |
!= | Strings are not equal | [ "$str1" != "$str2" ] | [[ "$str1" != "$str2" ]] |
-z | String is empty (length 0) | [ -z "$empty_str" ] | [[ -z "$empty_str" ]] |
-n | String is non-empty (length > 0) | [ -n "$non_empty_str" ] | [[ -n "$non_empty_str" ]] |
== *pattern* | String matches a pattern (Bash-only) | N/A (use case instead) | [[ "$str" == "he*" ]] (starts with “he”) |
Numeric Operators
Numeric operators compare integers. Use these with [ ] or [[ ]].
| Operator | Description | Example (with [ ]) | Example (with [[ ]]) |
|---|---|---|---|
-eq | Equal to | [ "$num" -eq 10 ] | [[ "$num" -eq 10 ]] or [[ "$num" == 10 ]] |
-ne | Not equal to | [ "$num" -ne 5 ] | [[ "$num" -ne 5 ]] or [[ "$num" != 5 ]] |
-gt | Greater than | [ "$num" -gt 20 ] | [[ "$num" -gt 20 ]] or [[ "$num" > 20 ]] |
-lt | Less than | [ "$num" -lt 15 ] | [[ "$num" -lt 15 ]] or [[ "$num" < 15 ]] |
-ge | Greater than or equal to | [ "$num" -ge 30 ] | [[ "$num" -ge 30 ]] or [[ "$num" >= 30 ]] |
-le | Less than or equal to | [ "$num" -le 50 ] | [[ "$num" -le 50 ]] or [[ "$num" <= 50 ]] |
Logical Operators
Combine multiple conditions with logical operators.
| Goal | [ ] Syntax (POSIX) | [[ ]] Syntax (Bash) |
|---|---|---|
| AND | [ condition1 ] && [ condition2 ] | [[ condition1 && condition2 ]] |
| OR | `[ condition1 ] | |
| NOT | [ ! condition ] | [[ ! condition ]] |
Example: AND Condition
# Check if file exists AND is readable (using [ ])
if [ -f "file.txt" ] && [ -r "file.txt" ]; then
echo "file.txt exists and is readable."
fi
# Same with [[ ]] (more concise)
if [[ -f "file.txt" && -r "file.txt" ]]; then
echo "file.txt exists and is readable."
fi
The case Statement
For checking a variable against multiple patterns, the case statement is cleaner than nested if-elif blocks. It uses pattern matching with * (wildcard), ? (single character), and [ ] (character ranges).
Syntax:
case "$variable" in
pattern1)
# Code if variable matches pattern1
;;
pattern2 | pattern3) # Match pattern2 OR pattern3
# Code if variable matches pattern2 or pattern3
;;
*) # Default case (matches anything else)
# Code if no patterns match
;;
esac
Example: Simple Menu
echo "Choose an option:"
echo "1. Say Hello"
echo "2. Say Goodbye"
read -p "Enter 1 or 2: " choice
case "$choice" in
1)
echo "Hello!"
;;
2)
echo "Goodbye!"
;;
*) # Default: invalid input
echo "Error: Invalid option."
exit 1
;;
esac
Practical Examples
Example 1: Check File Existence and Permissions
This script verifies if a file exists, is readable, and is not empty before processing it.
#!/bin/bash
file="data.txt"
# Check if file exists
if [ ! -e "$file" ]; then
echo "Error: $file does not exist."
exit 1
fi
# Check if file is readable
if [ ! -r "$file" ]; then
echo "Error: $file is not readable."
exit 1
fi
# Check if file is not empty
if [ -s "$file" ]; then
echo "Processing $file..."
# Add your processing logic here (e.g., cat, grep)
else
echo "Warning: $file is empty. No processing needed."
fi
Example 2: Validate User Input
This script prompts the user for a number and ensures the input is a positive integer.
#!/bin/bash
read -p "Enter a positive integer: " num
# Check if input is empty
if [ -z "$num" ]; then
echo "Error: Input cannot be empty."
exit 1
fi
# Check if input is a valid integer (using regex in [[ ]])
if [[ ! "$num" =~ ^[0-9]+$ ]]; then
echo "Error: '$num' is not a positive integer."
exit 1
fi
echo "You entered: $num (valid positive integer)."
Example 3: Menu-Driven Script with case
A script to manage a to-do list with add, view, and delete options.
#!/bin/bash
todo_file="todo.txt"
while true; do # Loop until user chooses exit
echo -e "\nTo-Do List Manager"
echo "1. Add Task"
echo "2. View Tasks"
echo "3. Delete All Tasks"
echo "4. Exit"
read -p "Choose an option (1-4): " choice
case "$choice" in
1)
read -p "Enter task: " task
echo "$task" >> "$todo_file"
echo "Task added."
;;
2)
if [ -s "$todo_file" ]; then
echo -e "\nYour Tasks:"
cat -n "$todo_file" # Show line numbers
else
echo "No tasks yet!"
fi
;;
3)
rm -f "$todo_file" # Delete file (if exists)
echo "All tasks deleted."
;;
4)
echo "Exiting..."
exit 0
;;
*)
echo "Invalid option. Enter 1-4."
;;
esac
done
Best Practices
- Quote Variables: Always quote variables in conditions (e.g.,
[ "$var" = "value" ]) to avoid word-splitting if the variable contains spaces. - Prefer
[[ ]]for Bash Scripts: Use[[ ]]for better readability, pattern matching, and fewer edge cases (e.g.,[[ "$var" == a* ]]). - Use
casefor Multiple Patterns:caseis cleaner thanif-elifwhen checking a variable against many values. - Check Exit Codes: Use
$?to check the exit status of commands (e.g.,command; if [ $? -eq 0 ]; then ...), but preferif command; then ...for brevity. - Avoid Nested
ifWhen Possible: Usecaseor logical operators to simplify nested conditions.
Conclusion
Conditional statements are the backbone of dynamic Bash scripts, enabling decision-making based on file properties, user input, and system state. By mastering if, if-else, if-elif-else, case, and operators like [ ]/[[ ]], you can write scripts that handle edge cases, validate inputs, and automate complex workflows.
Practice with real-world examples (like the ones above) to build intuition, and remember to prioritize readability and portability. With these tools, you’ll be well on your way to writing robust Bash scripts!