
if-else in Shell Scripts – Conditional Logic in Bash
Conditional logic forms the backbone of intelligent shell scripting, allowing your bash scripts to make decisions, handle different scenarios, and automate complex workflows based on dynamic conditions. Whether you’re a system administrator automating server maintenance tasks or a developer building deployment pipelines, mastering if-else statements in bash will dramatically improve your scripting capabilities. This comprehensive guide will walk you through everything from basic conditional syntax to advanced use cases, troubleshooting common issues, and implementing best practices that will make your shell scripts more robust and maintainable.
Understanding Bash Conditional Logic
Bash conditional statements evaluate expressions and execute different code blocks based on whether conditions are true or false. The fundamental structure relies on exit codes – in Unix systems, a command returns 0 for success (true) and non-zero values for failure (false). This concept extends beyond simple command execution to complex logical operations using test operators.
The basic if-else syntax follows this pattern:
if [ condition ]; then
# Commands to execute if condition is true
elif [ another_condition ]; then
# Commands to execute if another_condition is true
else
# Commands to execute if all conditions are false
fi
Bash provides multiple ways to write conditional statements, including the traditional [ ]
syntax, the enhanced [[ ]]
construct, and the test
command. Each has specific use cases and capabilities:
Syntax | Features | Best Use Case | POSIX Compliant |
---|---|---|---|
[ condition ] |
Basic comparisons, portable | Simple checks, cross-shell compatibility | Yes |
[[ condition ]] |
Pattern matching, logical operators, safer | Complex conditions, bash-specific scripts | No |
test condition |
Identical to [ ], explicit syntax | Educational purposes, very old systems | Yes |
Step-by-Step Implementation Guide
Let’s build conditional logic skills progressively, starting with basic examples and advancing to complex scenarios you’ll encounter in production environments.
Basic String and Numeric Comparisons
#!/bin/bash
# String comparison examples
username="admin"
if [ "$username" = "admin" ]; then
echo "Administrator access granted"
else
echo "Regular user access"
fi
# Numeric comparison examples
server_load=85
if [ $server_load -gt 90 ]; then
echo "High server load detected: $server_load%"
# Trigger alert or load balancing
elif [ $server_load -gt 70 ]; then
echo "Moderate server load: $server_load%"
# Log warning
else
echo "Server load normal: $server_load%"
fi
File and Directory Operations
#!/bin/bash
# Check if configuration file exists and is readable
config_file="/etc/myapp/config.conf"
if [ -f "$config_file" ] && [ -r "$config_file" ]; then
echo "Loading configuration from $config_file"
source "$config_file"
elif [ -f "$config_file" ]; then
echo "Error: Configuration file exists but is not readable"
exit 1
else
echo "Warning: Configuration file not found, using defaults"
# Set default values
DEBUG_MODE=false
LOG_LEVEL="info"
fi
# Directory backup logic
backup_dir="/var/backups/$(date +%Y%m%d)"
if [ ! -d "$backup_dir" ]; then
mkdir -p "$backup_dir"
echo "Created backup directory: $backup_dir"
fi
Advanced Pattern Matching with Double Brackets
#!/bin/bash
# Enhanced conditional with [[ ]]
log_level="DEBUG"
environment="production"
if [[ "$environment" == "production" && "$log_level" =~ ^(ERROR|WARN)$ ]]; then
echo "Production logging configured correctly"
elif [[ "$environment" == "development" ]]; then
echo "Development environment - all log levels allowed"
else
echo "Warning: Unusual configuration detected"
fi
# File extension checking
filename="deployment.tar.gz"
if [[ "$filename" == *.tar.gz ]]; then
echo "Extracting compressed archive..."
tar -xzf "$filename"
elif [[ "$filename" == *.zip ]]; then
echo "Extracting ZIP archive..."
unzip "$filename"
else
echo "Unsupported file format"
fi
Real-World Examples and Use Cases
Server Health Monitoring Script
#!/bin/bash
# Comprehensive server monitoring with conditional logic
check_disk_space() {
disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $disk_usage -gt 95 ]; then
echo "CRITICAL: Disk usage at ${disk_usage}%"
# Send alert to monitoring system
curl -X POST "https://monitoring.example.com/alerts" \
-d "level=critical&message=Disk space critical"
return 2
elif [ $disk_usage -gt 85 ]; then
echo "WARNING: Disk usage at ${disk_usage}%"
return 1
else
echo "OK: Disk usage at ${disk_usage}%"
return 0
fi
}
check_memory_usage() {
mem_usage=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}')
if [ $mem_usage -gt 90 ]; then
echo "CRITICAL: Memory usage at ${mem_usage}%"
# Restart memory-intensive services if needed
if pgrep -f "heavy_process" > /dev/null; then
echo "Restarting heavy processes..."
systemctl restart heavy_process
fi
return 2
elif [ $mem_usage -gt 80 ]; then
echo "WARNING: Memory usage at ${mem_usage}%"
return 1
else
echo "OK: Memory usage at ${mem_usage}%"
return 0
fi
}
# Main monitoring logic
echo "Starting server health check..."
disk_status=0
memory_status=0
check_disk_space
disk_status=$?
check_memory_usage
memory_status=$?
if [ $disk_status -eq 2 ] || [ $memory_status -eq 2 ]; then
echo "CRITICAL issues detected - triggering emergency procedures"
exit 2
elif [ $disk_status -eq 1 ] || [ $memory_status -eq 1 ]; then
echo "WARNING conditions present - monitoring closely"
exit 1
else
echo "All systems normal"
exit 0
fi
Deployment Pipeline with Conditional Logic
#!/bin/bash
# Advanced deployment script with comprehensive error handling
ENVIRONMENT=${1:-staging}
VERSION=${2:-latest}
ROLLBACK_VERSION=""
validate_environment() {
if [[ ! "$ENVIRONMENT" =~ ^(development|staging|production)$ ]]; then
echo "Error: Invalid environment '$ENVIRONMENT'"
echo "Valid options: development, staging, production"
exit 1
fi
}
pre_deployment_checks() {
echo "Running pre-deployment checks for $ENVIRONMENT..."
# Check if target servers are accessible
if [ "$ENVIRONMENT" = "production" ]; then
servers=("prod-web-01" "prod-web-02" "prod-db-01")
else
servers=("staging-web-01")
fi
for server in "${servers[@]}"; do
if ! ping -c 1 "$server" &> /dev/null; then
echo "Error: Cannot reach server $server"
exit 1
fi
done
# Verify application health before deployment
if [ "$ENVIRONMENT" = "production" ]; then
health_url="https://api.production.example.com/health"
else
health_url="https://api.staging.example.com/health"
fi
if ! curl -f "$health_url" &> /dev/null; then
echo "Warning: Health check failed, but proceeding with deployment"
fi
}
deploy_application() {
echo "Deploying version $VERSION to $ENVIRONMENT..."
# Store current version for potential rollback
if [ -f "/opt/myapp/VERSION" ]; then
ROLLBACK_VERSION=$(cat /opt/myapp/VERSION)
echo "Current version: $ROLLBACK_VERSION (available for rollback)"
fi
# Different deployment strategies based on environment
if [ "$ENVIRONMENT" = "production" ]; then
echo "Using blue-green deployment for production..."
deploy_blue_green
else
echo "Using rolling deployment for $ENVIRONMENT..."
deploy_rolling
fi
}
deploy_blue_green() {
# Simulate blue-green deployment logic
if docker ps | grep -q "myapp-blue"; then
active_color="blue"
deploy_color="green"
else
active_color="green"
deploy_color="blue"
fi
echo "Deploying to $deploy_color environment..."
docker run -d --name "myapp-$deploy_color" "myapp:$VERSION"
# Health check new deployment
sleep 30
if curl -f "http://localhost:8080/health" &> /dev/null; then
echo "New deployment healthy, switching traffic..."
# Switch load balancer configuration
switch_traffic "$deploy_color"
docker stop "myapp-$active_color"
docker rm "myapp-$active_color"
else
echo "New deployment failed health check, rolling back..."
docker stop "myapp-$deploy_color"
docker rm "myapp-$deploy_color"
exit 1
fi
}
deploy_rolling() {
# Simulate rolling deployment
echo "Stopping application..."
systemctl stop myapp
echo "Updating application files..."
cp "/tmp/myapp-$VERSION.tar.gz" /opt/
cd /opt
tar -xzf "myapp-$VERSION.tar.gz"
echo "Starting application..."
systemctl start myapp
# Verify deployment
sleep 10
if systemctl is-active --quiet myapp; then
echo "Application started successfully"
echo "$VERSION" > /opt/myapp/VERSION
else
echo "Application failed to start, attempting rollback..."
if [ -n "$ROLLBACK_VERSION" ]; then
rollback_deployment "$ROLLBACK_VERSION"
else
echo "No rollback version available"
exit 1
fi
fi
}
rollback_deployment() {
local rollback_version=$1
echo "Rolling back to version $rollback_version..."
systemctl stop myapp
cd /opt
tar -xzf "myapp-$rollback_version.tar.gz"
systemctl start myapp
if systemctl is-active --quiet myapp; then
echo "Rollback successful"
echo "$rollback_version" > /opt/myapp/VERSION
else
echo "Rollback failed - manual intervention required"
exit 1
fi
}
# Main deployment workflow
validate_environment
pre_deployment_checks
deploy_application
echo "Deployment completed successfully for $ENVIRONMENT environment"
Performance Considerations and Comparisons
Understanding the performance characteristics of different conditional constructs helps optimize script execution, especially in loops or frequently called functions:
Operation | [ ] syntax | [[ ]] syntax | Performance Impact |
---|---|---|---|
String comparison | External command | Bash built-in | [[ ]] ~3x faster |
Pattern matching | Not supported | Native support | [[ ]] significantly faster |
Multiple conditions | Requires && || | Built-in operators | [[ ]] ~2x faster |
Variable safety | Requires quoting | Automatic handling | [[ ]] prevents errors |
Performance Testing Example
#!/bin/bash
# Performance comparison script
test_single_bracket() {
local start_time=$(date +%s%N)
for i in {1..10000}; do
if [ "$i" -gt 5000 ]; then
: # no-op
fi
done
local end_time=$(date +%s%N)
echo "Single bracket test: $(( (end_time - start_time) / 1000000 ))ms"
}
test_double_bracket() {
local start_time=$(date +%s%N)
for i in {1..10000}; do
if [[ $i -gt 5000 ]]; then
: # no-op
fi
done
local end_time=$(date +%s%N)
echo "Double bracket test: $(( (end_time - start_time) / 1000000 ))ms"
}
echo "Performance comparison (10,000 iterations):"
test_single_bracket
test_double_bracket
Best Practices and Common Pitfalls
Essential Best Practices
- Always quote variables when using single brackets to prevent word splitting and pathname expansion
- Use [[ ]] for bash-specific scripts as it provides better safety and performance
- Implement proper error handling with meaningful exit codes and error messages
- Test edge cases including empty variables, special characters, and boundary conditions
- Use meaningful variable names and add comments for complex conditional logic
Common Pitfalls and Solutions
# WRONG: Unquoted variables can cause syntax errors
if [ $user_input = "admin" ]; then
echo "Admin user"
fi
# CORRECT: Always quote variables
if [ "$user_input" = "admin" ]; then
echo "Admin user"
fi
# WRONG: Using = instead of == in [[ ]]
if [[ $status = "active" ]]; then
echo "Status is active"
fi
# CORRECT: Use == for string comparison in [[ ]]
if [[ "$status" == "active" ]]; then
echo "Status is active"
fi
# WRONG: Not handling empty variables
if [ $config_value -gt 10 ]; then
echo "Value is high"
fi
# CORRECT: Check if variable is set and numeric
if [[ -n "$config_value" && "$config_value" =~ ^[0-9]+$ ]] && [ "$config_value" -gt 10 ]; then
echo "Value is high"
fi
Security Considerations
#!/bin/bash
# Secure input validation example
validate_user_input() {
local input="$1"
# Check for command injection attempts
if [[ "$input" =~ [;&\|`$] ]]; then
echo "Error: Invalid characters in input"
return 1
fi
# Limit input length
if [ ${#input} -gt 100 ]; then
echo "Error: Input too long"
return 1
fi
# Whitelist allowed characters
if [[ ! "$input" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "Error: Input contains invalid characters"
return 1
fi
return 0
}
# Safe file operations
safe_file_operation() {
local filename="$1"
# Prevent directory traversal
if [[ "$filename" =~ \.\./|^/ ]]; then
echo "Error: Invalid file path"
return 1
fi
# Check if file exists and is within allowed directory
local allowed_dir="/var/www/uploads"
local full_path="$allowed_dir/$filename"
if [[ "$full_path" != "$allowed_dir"/* ]]; then
echo "Error: File outside allowed directory"
return 1
fi
if [ -f "$full_path" ]; then
echo "Processing file: $full_path"
# Perform safe operations
else
echo "File not found: $filename"
return 1
fi
}
Integration with System Administration
For system administrators managing server infrastructure, conditional logic becomes crucial when automating maintenance tasks, monitoring system health, and managing deployments on VPS or dedicated servers.
Automated Backup Script with Intelligent Logic
#!/bin/bash
# Intelligent backup script with conditional logic
BACKUP_BASE="/var/backups"
MAX_BACKUPS=7
DATABASE_NAME="production_db"
WEB_ROOT="/var/www/html"
# Check available disk space before backup
check_disk_space() {
local required_space=$1 # in GB
local available_space=$(df /var/backups | awk 'NR==2 {print int($4/1024/1024)}')
if [ $available_space -lt $required_space ]; then
echo "Error: Insufficient disk space. Required: ${required_space}GB, Available: ${available_space}GB"
# Try to clean old backups
echo "Attempting to clean old backups..."
cleanup_old_backups
# Recheck space
available_space=$(df /var/backups | awk 'NR==2 {print int($4/1024/1024)}')
if [ $available_space -lt $required_space ]; then
echo "Error: Still insufficient space after cleanup"
return 1
fi
fi
return 0
}
cleanup_old_backups() {
local backup_count=$(ls -1 "$BACKUP_BASE"/backup_* 2>/dev/null | wc -l)
if [ $backup_count -gt $MAX_BACKUPS ]; then
local files_to_remove=$((backup_count - MAX_BACKUPS))
echo "Removing $files_to_remove old backup(s)..."
ls -1t "$BACKUP_BASE"/backup_* | tail -n $files_to_remove | while read -r backup_file; do
echo "Removing old backup: $backup_file"
rm -rf "$backup_file"
done
fi
}
perform_database_backup() {
local backup_dir="$1"
echo "Starting database backup..."
if command -v mysqldump >/dev/null 2>&1; then
if mysqldump "$DATABASE_NAME" > "$backup_dir/database.sql" 2>/dev/null; then
echo "Database backup completed successfully"
gzip "$backup_dir/database.sql"
return 0
else
echo "Error: Database backup failed"
return 1
fi
else
echo "Warning: mysqldump not found, skipping database backup"
return 0
fi
}
perform_files_backup() {
local backup_dir="$1"
echo "Starting files backup..."
if [ -d "$WEB_ROOT" ]; then
if tar -czf "$backup_dir/files.tar.gz" -C "$(dirname "$WEB_ROOT")" "$(basename "$WEB_ROOT")" 2>/dev/null; then
echo "Files backup completed successfully"
return 0
else
echo "Error: Files backup failed"
return 1
fi
else
echo "Warning: Web root directory not found, skipping files backup"
return 0
fi
}
# Main backup logic
main() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_dir="$BACKUP_BASE/backup_$timestamp"
local backup_success=true
echo "Starting backup process at $(date)"
# Pre-flight checks
if ! check_disk_space 5; then
echo "Backup aborted due to insufficient disk space"
exit 1
fi
# Create backup directory
if ! mkdir -p "$backup_dir"; then
echo "Error: Cannot create backup directory"
exit 1
fi
# Perform backups
if ! perform_database_backup "$backup_dir"; then
backup_success=false
fi
if ! perform_files_backup "$backup_dir"; then
backup_success=false
fi
# Final status and cleanup
if [ "$backup_success" = true ]; then
echo "Backup completed successfully: $backup_dir"
cleanup_old_backups
else
echo "Backup completed with errors: $backup_dir"
# Keep the backup even with errors for manual inspection
fi
# Generate backup report
if [ -d "$backup_dir" ]; then
local backup_size=$(du -sh "$backup_dir" | cut -f1)
echo "Backup size: $backup_size"
echo "Backup location: $backup_dir"
# Send notification if configured
if [ -n "$NOTIFICATION_EMAIL" ]; then
local subject="Backup Report - $(hostname)"
local message="Backup completed on $(date). Size: $backup_size. Location: $backup_dir"
echo "$message" | mail -s "$subject" "$NOTIFICATION_EMAIL"
fi
fi
}
# Run main function
main "$@"
Mastering conditional logic in bash scripts transforms basic automation into intelligent, responsive systems that can handle complex scenarios gracefully. The combination of proper error handling, security considerations, and performance optimization makes your scripts production-ready for enterprise environments. For additional information on bash scripting best practices, refer to the official Bash manual and the Advanced Bash-Scripting Guide.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.