BLOG POSTS
    MangoHost Blog / if-else in Shell Scripts – Conditional Logic in Bash
if-else in Shell Scripts – Conditional Logic in Bash

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.

Leave a reply

Your email address will not be published. Required fields are marked