Shell Scripting Conditionals: If, Case & Logic Explained
Every useful script needs to make decisions. Should we proceed with the deployment? Is the backup file valid? Does the user have permission? Conditional statements are the decision-making engine of shell scripting, allowing your code to branch based on tests, comparisons, and logic.
In this guide, we'll cover everything from basic if-else blocks to advanced pattern matching with [[ ]], giving you the tools to write intelligent, robust Bash scripts.
If-Else Statements
The if statement evaluates a condition and executes code based on whether that condition is true or false.
Basic If-Else Structure
1#!/bin/bash 2 3read -p "Enter a number: " num 4 5if [ "$num" -gt 10 ]; then 6 echo "$num is greater than 10" 7elif [ "$num" -eq 10 ]; then 8 echo "$num is equal to 10" 9else 10 echo "$num is less than 10" 11fi
How it works:
if [ condition ]; then— starts the block if the condition is trueelif [ condition ]; then— checks an alternative condition (optional)else— catches everything else (optional)fi— closes the if block (yes, it'sifspelled backwards)
Critical: Always put spaces around the brackets
[ ]and operators.[ "$num" -gt 10 ]works;["$num" -gt 10]does not.
Test Operators
Bash provides three categories of test operators: numeric comparisons, string comparisons, and file tests.
Numeric Comparisons
Use these when comparing integers:
| Operator | Meaning | Example |
|---|---|---|
-eq | Equal | [ "$a" -eq "$b" ] |
-ne | Not equal | [ "$a" -ne "$b" ] |
-gt | Greater than | [ "$a" -gt 10 ] |
-ge | Greater than or equal | [ "$a" -ge "$b" ] |
-lt | Less than | [ "$a" -lt 100 ] |
-le | Less than or equal | [ "$a" -le "$b" ] |
1#!/bin/bash 2 3cpu_usage=75 4threshold=80 5 6if [ "$cpu_usage" -ge "$threshold" ]; then 7 echo "WARNING: CPU usage is at ${cpu_usage}%!" 8else 9 echo "CPU usage is normal: ${cpu_usage}%" 10fi
String Comparisons
| Operator | Meaning | Example |
|---|---|---|
= or == | Equal | [ "$a" = "$b" ] |
!= | Not equal | [ "$a" != "$b" ] |
-z | Zero length (empty) | [ -z "$str" ] |
-n | Non-zero length | [ -n "$str" ] |
1#!/bin/bash 2 3name="admin" 4 5if [ "$name" = "admin" ]; then 6 echo "Welcome, administrator!" 7fi 8 9# Check for empty string 10input="" 11 12if [ -z "$input" ]; then 13 echo "Error: Input cannot be empty." 14 exit 1 15fi
Always quote your variables in string comparisons. Without quotes, an empty variable can cause syntax errors:
[ $name = admin ]becomes[ = admin ]whennameis empty — which crashes.
File Tests
File tests are among the most frequently used conditionals in system administration scripts:
| Operator | Meaning | Example |
|---|---|---|
-e | File exists | [ -e "$file" ] |
-f | Regular file exists | [ -f "$file" ] |
-d | Directory exists | [ -d "$dir" ] |
-r | Readable | [ -r "$file" ] |
-w | Writable | [ -w "$file" ] |
-x | Executable | [ -x "$file" ] |
-s | Non-empty file | [ -s "$file" ] |
-L | Symbolic link | [ -L "$link" ] |
Practical File Check Script
1#!/bin/bash 2 3file="config.ini" 4 5if [ -e "$file" ]; then 6 echo "File '$file' exists." 7 8 if [ -r "$file" ]; then 9 echo " ✓ Readable" 10 fi 11 12 if [ -w "$file" ]; then 13 echo " ✓ Writable" 14 fi 15 16 if [ -x "$file" ]; then 17 echo " ✓ Executable" 18 fi 19else 20 echo "Error: '$file' not found!" 21 exit 1 22fi
Logical Operators
Sometimes you need to test multiple conditions at once. Bash supports && (AND), || (OR), and ! (NOT).
AND (&&) — Both Must Be True
1#!/bin/bash 2 3config="app.conf" 4 5if [ -f "$config" ] && [ -r "$config" ]; then 6 echo "Config file exists and is readable." 7else 8 echo "Config file missing or not readable." 9fi
OR (||) — At Least One Must Be True
1#!/bin/bash 2 3os="ubuntu" 4 5if [ "$os" = "ubuntu" ] || [ "$os" = "debian" ]; then 6 echo "APT-based system detected." 7else 8 echo "Using a different package manager." 9fi
NOT (!) — Negate the Condition
1#!/bin/bash 2 3backup_dir="/backups" 4 5if [ ! -d "$backup_dir" ]; then 6 echo "Backup directory does not exist. Creating it..." 7 mkdir -p "$backup_dir" 8fi
Combining Logic
You can chain multiple conditions for complex logic:
1#!/bin/bash 2 3file="data.csv" 4 5if [[ -f "$file" && ( "$USER" = "admin" || "$USER" = "root" ) ]]; then 6 echo "You have permission to process $file" 7fi
Case Statements
When you have multiple discrete values to check, case is cleaner and more readable than a long chain of if-elif-else.
Basic Case Syntax
1#!/bin/bash 2 3read -p "Enter your OS (linux/mac/windows): " os 4 5case $os in 6 linux|Linux|LINUX) 7 echo "You chose Linux. Package manager: apt/yum/dnf" 8 ;; 9 mac|Mac|MAC|darwin) 10 echo "You chose macOS. Package manager: brew" 11 ;; 12 windows|Windows|WINDOWS) 13 echo "You chose Windows. Package manager: winget/choco" 14 ;; 15 *) 16 echo "Unknown OS: $os" 17 ;; 18esac
How it works:
case $variable in— starts the blockpattern)— matches the value (supports wildcards with*,?, and[]);;— terminates the commands for that pattern*)— default case (likeelse)esac— ends the case block
Case with Wildcards
1#!/bin/bash 2 3read -p "Enter a filename: " filename 4 5case "$filename" in 6 *.txt) 7 echo "Text file detected." 8 ;; 9 *.jpg|*.jpeg|*.png|*.gif) 10 echo "Image file detected." 11 ;; 12 *.sh) 13 echo "Shell script detected." 14 ;; 15 *) 16 echo "Unknown file type." 17 ;; 18esac
Practical Example: Service Control Script
1#!/bin/bash 2 3service="nginx" 4action="${1:-status}" # Default to 'status' if no argument 5 6case "$action" in 7 start) 8 sudo systemctl start "$service" 9 echo "$service started." 10 ;; 11 stop) 12 sudo systemctl stop "$service" 13 echo "$service stopped." 14 ;; 15 restart) 16 sudo systemctl restart "$service" 17 echo "$service restarted." 18 ;; 19 status) 20 sudo systemctl status "$service" 21 ;; 22 *) 23 echo "Usage: $0 {start|stop|restart|status}" 24 exit 1 25 ;; 26esac
Double Brackets [[ ]]
The [[ ]] syntax is a modern Bash enhancement over the traditional [ ] test command. It's more powerful, safer, and should be your default choice for complex conditions.
Why Prefer [[ ]] Over [ ]?
| Feature | [ ] | [[ ]] |
|---|---|---|
| Word splitting on unquoted vars | Yes (dangerous) | No (safe) |
| Glob expansion | Yes (dangerous) | No (safe) |
| Regex matching | No | Yes (=~) |
Pattern matching (==) | No | Yes |
| Logical operators | -a, -o | &&, || |
| String comparison | = | == or = |
Pattern Matching with ==
1#!/bin/bash 2 3filename="report_2026.pdf" 4 5if [[ "$filename" == *.pdf ]]; then 6 echo "This is a PDF file." 7fi 8 9if [[ "$filename" == report_* ]]; then 10 echo "This is a report file." 11fi
Regex Matching with =~
This is where [[ ]] truly shines. You can match strings against regular expressions:
1#!/bin/bash 2 3email="user@example.com" 4 5if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then 6 echo "Valid email format." 7else 8 echo "Invalid email format." 9fi
More Regex Examples
1#!/bin/bash 2 3read -p "Enter a hex color code (#RRGGBB): " color 4 5if [[ "$color" =~ ^#[0-9A-Fa-f]{6}$ ]]; then 6 echo "Valid hex color: $color" 7else 8 echo "Invalid format. Use #RRGGBB (e.g., #FF5733)" 9fi
1#!/bin/bash 2 3ip="192.168.1.1" 4 5if [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 6 echo "Valid IPv4 format." 7else 8 echo "Invalid IP format." 9fi
Safe String Comparison
With [[ ]], you don't need to worry about empty strings causing syntax errors:
1#!/bin/bash 2 3# This is SAFE even if 'name' is empty 4name="" 5 6if [[ "$name" == admin ]]; then 7 echo "Admin user" 8else 9 echo "Not admin (or empty)" 10fi
The same code with [ ] would fail if name were unquoted and empty.
Real-World Example: Deployment Validation Script
Let's combine everything into a production-ready script that validates before deploying:
1#!/bin/bash 2 3set -euo pipefail 4 5APP_DIR="/opt/myapp" 6REQUIRED_SPACE_MB=500 7ENV="${1:-production}" 8 9echo "=== Pre-Deployment Validation ===" 10echo "Environment: $ENV" 11echo "" 12 13# Validate environment argument 14if [[ "$ENV" != "production" && "$ENV" != "staging" && "$ENV" != "development" ]]; then 15 echo "Error: Invalid environment. Use: production, staging, or development" 16 exit 1 17fi 18 19# Check if application directory exists 20if [[ ! -d "$APP_DIR" ]]; then 21 echo "Error: Application directory $APP_DIR does not exist." 22 exit 1 23fi 24 25# Check disk space 26available_mb=$(df -m "$APP_DIR" | awk 'NR==2 {print $4}') 27 28if [[ "$available_mb" -lt "$REQUIRED_SPACE_MB" ]]; then 29 echo "Error: Insufficient disk space." 30 echo "Required: ${REQUIRED_SPACE_MB}MB, Available: ${available_mb}MB" 31 exit 1 32fi 33 34echo "✓ Disk space sufficient (${available_mb}MB available)" 35 36# Check if required config file exists and is readable 37config_file="$APP_DIR/config.$ENV.yml" 38 39if [[ ! -f "$config_file" ]]; then 40 echo "Error: Config file not found: $config_file" 41 exit 1 42elif [[ ! -r "$config_file" ]]; then 43 echo "Error: Config file not readable: $config_file" 44 exit 1 45fi 46 47echo "✓ Config file valid" 48 49# Check if port is available 50port=8080 51if ss -tlnp | grep -q ":$port "; then 52 echo "Warning: Port $port is already in use." 53 read -n 1 -p "Continue anyway? [y/n]: " confirm 54 echo "" 55 if [[ "$confirm" != [Yy] ]]; then 56 echo "Deployment cancelled." 57 exit 0 58 fi 59else 60 echo "✓ Port $port is available" 61fi 62 63# Environment-specific checks 64case "$ENV" in 65 production) 66 if [[ "$USER" != "deploy" ]]; then 67 echo "Error: Production deployments must run as 'deploy' user." 68 exit 1 69 fi 70 echo "✓ Production user verified" 71 ;; 72 staging) 73 echo "ℹ Staging mode: skipping production checks" 74 ;; 75 development) 76 echo "ℹ Development mode: relaxed checks" 77 ;; 78esac 79 80echo "" 81echo "✅ All validations passed. Ready for deployment."
Best Practices for Conditionals
- Always use
[[ ]]for new Bash scripts — it's safer and more powerful than[ ] - Quote every variable inside tests:
[[ "$var" == "value" ]]not[[ $var == value ]] - Use
-zand-nfor emptiness checks instead of comparing to empty strings - Prefer
caseover longif-elifchains when checking discrete values - Use
set -eso your script exits on failed commands, but don't rely on it for all error handling - Write explicit error messages — tell the user why something failed, not just that it failed
- Exit with non-zero codes on failure so calling scripts or CI/CD pipelines know something went wrong
Quick Reference
1# Numeric comparison 2[ "$a" -eq "$b" ] # Equal 3[ "$a" -ne "$b" ] # Not equal 4[ "$a" -gt "$b" ] # Greater than 5[ "$a" -lt "$b" ] # Less than 6 7# String comparison 8[ "$a" = "$b" ] # Equal 9[ "$a" != "$b" ] # Not equal 10[ -z "$a" ] # Empty 11[ -n "$a" ] # Not empty 12 13# File tests 14[ -e "$file" ] # Exists 15[ -f "$file" ] # Regular file 16[ -d "$dir" ] # Directory 17[ -r "$file" ] # Readable 18[ -w "$file" ] # Writable 19[ -x "$file" ] # Executable 20[ -s "$file" ] # Non-empty 21 22# Logical operators 23[ "$a" -gt 0 ] && [ "$a" -lt 100 ] # AND 24[ "$a" = "x" ] || [ "$a" = "y" ] # OR 25[ ! -f "$file" ] # NOT 26 27# Double brackets (preferred) 28[[ "$str" == *.txt ]] # Pattern match 29[[ "$email" =~ ^.+@.+\..+$ ]] # Regex match 30[[ -f "$file" && "$USER" == "root" ]] # Combined logic 31 32# Case statement 33case "$var" in 34 a|A) echo "A" ;; 35 b) echo "B" ;; 36 *) echo "Other" ;; 37esac
Conditionals are where your scripts transition from simple command runners to intelligent automation tools. Master if-else for branching, case for multi-way decisions, and [[ ]] for safe, expressive tests — and you'll be equipped to handle virtually any scripting scenario.
Next up: Loops and iteration — the key to processing data at scale without repetition.