Skip to main content

Shell Scripting Mastery - DevOps Automation Guide

 Shell Scripting: The DevOps Automation Powerhouse

Master Bash scripting to automate tasks, manage systems, and become a more efficient DevOps engineer.

📅 Published: Feb 2026
⏱️ Estimated Reading Time: 22 minutes
🏷️ Tags: Bash Scripting, Shell Scripting, Automation, DevOps, Cron, Scripting


🐚 Introduction to Bash Scripting: Your Automation Superpower

What is Shell Scripting?

Shell scripting is like writing recipes for your computer. Instead of manually typing commands one by one, you write them in a file, and the computer executes them automatically. It's the secret weapon of every efficient system administrator and DevOps engineer.

Think of it this way:

  • Manual commands = Cooking by following a recipe step-by-step each time

  • Shell script = Having a robot chef that knows the entire recipe and can cook it perfectly every time

Why Learn Shell Scripting?

  1. Automation - Make repetitive tasks happen automatically

  2. Consistency - Same commands run the same way every time

  3. Time-saving - Write once, run infinitely

  4. Documentation - Scripts document how things are done

  5. Error reduction - Fewer manual typing errors

Your First Shell Script

Let's create the classic "Hello World" script:

bash
#!/bin/bash
# This is a comment - it's not executed
# File: hello.sh

echo "Hello, World!"
echo "Today is: $(date)"
echo "You are: $(whoami)"

Breaking it down:

  • #!/bin/bash - This is called a shebang. It tells the system to use Bash to run this script.

  • # - Anything after # is a comment (ignored by the computer)

  • echo - Prints text to the screen

  • $(command) - Runs the command inside and uses its output

Making and Running Scripts

bash
# 1. Create the script file
nano hello.sh

# 2. Add the content above

# 3. Make it executable
chmod +x hello.sh

# 4. Run it
./hello.sh
# Output:
# Hello, World!
# Today is: Mon Feb 10 10:30:00 UTC 2026
# You are: yourusername

# Alternative ways to run:
bash hello.sh
sh hello.sh
source hello.sh  # Runs in current shell (for functions/variables)

Important: The ./ before the filename tells Linux "look in the current directory for this program." Without it, Linux looks in system directories like /bin/ and /usr/bin/.


📦 Variables, Loops, and Conditionals: The Building Blocks

Variables: Storing Information

Variables are like labeled boxes where you store information to use later.

bash
#!/bin/bash
# File: variables.sh

# Define variables
NAME="Alice"
AGE=30
SERVER="web01.example.com"
PATH="/home/user/documents"

# Use variables
echo "Hello, $NAME"
echo "You are $AGE years old"

# Quotes matter!
echo "Server: $SERVER"      # Works
echo 'Server: $SERVER'      # Doesn't expand variable
echo "Path: $PATH"          # Uses system PATH variable (be careful!)
echo "Path: ${PATH}"        # Same as above
echo "My path: $MY_PATH"    # Better: Use different variable names

# Command output in variables
CURRENT_DATE=$(date)
UPTIME=$(uptime -p)
USER_COUNT=$(who | wc -l)

echo "Date: $CURRENT_DATE"
echo "System uptime: $UPTIME"
echo "Users logged in: $USER_COUNT"

# Arithmetic operations
COUNT=10
COUNT=$((COUNT + 1))
echo "Count: $COUNT"

# Increment shortcut
((COUNT++))
echo "Count after increment: $COUNT"

User Input: Making Scripts Interactive

bash
#!/bin/bash
# File: input.sh

# Ask for user input
read -p "What is your name? " USERNAME
read -sp "Enter password: " PASSWORD  # -s = silent (no echo)
echo
read -p "Enter age: " AGE

echo "Hello $USERNAME, you are $AGE years old"
echo "Password length: ${#PASSWORD} characters"  # ${#VAR} = length

# Read multiple values
read -p "Enter three numbers: " NUM1 NUM2 NUM3
echo "You entered: $NUM1, $NUM2, $NUM3"

# Read entire line
read -p "Enter a sentence: " SENTENCE
echo "You said: $SENTENCE"

Conditionals: Making Decisions

Conditionals let your script make decisions based on conditions.

bash
#!/bin/bash
# File: conditions.sh

# Simple if statement
read -p "Enter a number: " NUMBER

if [ $NUMBER -gt 10 ]; then
    echo "$NUMBER is greater than 10"
fi

# If-else
if [ $NUMBER -gt 10 ]; then
    echo "$NUMBER is greater than 10"
else
    echo "$NUMBER is 10 or less"
fi

# If-elif-else
if [ $NUMBER -gt 100 ]; then
    echo "$NUMBER is greater than 100"
elif [ $NUMBER -gt 50 ]; then
    echo "$NUMBER is between 51 and 100"
elif [ $NUMBER -gt 10 ]; then
    echo "$NUMBER is between 11 and 50"
else
    echo "$NUMBER is 10 or less"
fi

# File tests
FILE="/etc/passwd"

if [ -f "$FILE" ]; then
    echo "$FILE exists and is a regular file"
fi

if [ -d "/etc" ]; then
    echo "/etc is a directory"
fi

if [ -r "$FILE" ]; then
    echo "You can read $FILE"
fi

if [ -w "$FILE" ]; then
    echo "You can write to $FILE"
fi

if [ -x "/bin/bash" ]; then
    echo "/bin/bash is executable"
fi

Common Test Operators

bash
# Numeric comparisons
[ $A -eq $B ]  # Equal
[ $A -ne $B ]  # Not equal
[ $A -lt $B ]  # Less than
[ $A -le $B ]  # Less than or equal
[ $A -gt $B ]  # Greater than
[ $A -ge $B ]  # Greater than or equal

# String comparisons
[ "$STR1" = "$STR2" ]   # Equal
[ "$STR1" != "$STR2" ]  # Not equal
[ -z "$STR" ]           # String is empty
[ -n "$STR" ]           # String is not empty

# File tests
[ -f "file" ]   # Is a regular file
[ -d "dir" ]    # Is a directory
[ -e "file" ]   # Exists
[ -r "file" ]   # Readable
[ -w "file" ]   # Writable
[ -x "file" ]   # Executable
[ -s "file" ]   # Size > 0

Loops: Repeating Actions

Loops let you repeat actions multiple times.

bash
#!/bin/bash
# File: loops.sh

# For loop - through a list
echo "Counting to 5:"
for i in 1 2 3 4 5; do
    echo "Number: $i"
done

# For loop - through a range
echo "Counting 1 to 10:"
for i in {1..10}; do
    echo "Number: $i"
done

# For loop - with step
echo "Even numbers 0 to 10:"
for i in {0..10..2}; do
    echo "Number: $i"
done

# For loop - through files
echo "Files in current directory:"
for file in *; do
    echo "Found: $file"
done

# While loop - until condition is false
echo "Counting down from 5:"
COUNT=5
while [ $COUNT -gt 0 ]; do
    echo "Count: $COUNT"
    ((COUNT--))
done

# Until loop - until condition is true
echo "Waiting for file to appear..."
until [ -f "/tmp/ready.flag" ]; do
    echo "File not found, waiting..."
    sleep 2
done
echo "File found!"

# Infinite loop (with break)
echo "Press Ctrl+C to stop this loop"
while true; do
    echo "Looping..."
    sleep 1
done

Real Example: Processing Files

bash
#!/bin/bash
# File: process-logs.sh

LOG_DIR="/var/log"
BACKUP_DIR="/backup/logs"

# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"

# Process each .log file
for logfile in "$LOG_DIR"/*.log; do
    if [ -f "$logfile" ]; then
        # Get just the filename (without path)
        filename=$(basename "$logfile")
        
        echo "Processing: $filename"
        
        # Count lines
        line_count=$(wc -l < "$logfile")
        
        # Backup if large
        if [ $line_count -gt 1000 ]; then
            echo "  Backing up (lines: $line_count)"
            cp "$logfile" "$BACKUP_DIR/$filename.backup"
        fi
        
        # Check for errors
        error_count=$(grep -c -i "error\|fail" "$logfile")
        if [ $error_count -gt 0 ]; then
            echo "  WARNING: Found $error_count errors"
        fi
    fi
done

echo "Processing complete!"

🔧 Functions & Arguments: Creating Reusable Code

Functions: Your Code Building Blocks

Functions are like mini-scripts within your script. They let you reuse code without repeating yourself.

bash
#!/bin/bash
# File: functions.sh

# Define a simple function
say_hello() {
    echo "Hello from a function!"
    echo "The time is: $(date)"
}

# Call the function
say_hello

# Function with parameters
greet_person() {
    local name=$1  # local makes it only available in function
    local time_of_day=$2
    
    echo "Good $time_of_day, $name!"
}

# Call with arguments
greet_person "Alice" "morning"
greet_person "Bob" "evening"

# Function that returns a value (exit code)
is_file_exists() {
    local filename=$1
    
    if [ -f "$filename" ]; then
        return 0  # Success
    else
        return 1  # Failure
    fi
}

# Use the function
if is_file_exists "/etc/passwd"; then
    echo "File exists"
else
    echo "File does not exist"
fi

# Function that "returns" output
get_file_size() {
    local filename=$1
    if [ -f "$filename" ]; then
        stat -c%s "$filename"
    else
        echo "0"
    fi
}

# Capture function output
SIZE=$(get_file_size "/etc/passwd")
echo "File size: $SIZE bytes"

Script Arguments: Making Scripts Flexible

Script arguments let users pass information to your script when they run it.

bash
#!/bin/bash
# File: arguments.sh

# Special variables for arguments
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Third argument: $3"

# All arguments
echo "All arguments: $@"
echo "Number of arguments: $#"

# Loop through all arguments
echo "Processing arguments:"
for arg in "$@"; do
    echo "  Argument: $arg"
done

# Shift arguments (remove first one)
echo "Before shift: $@"
shift
echo "After shift: $@"

# Practical example: Process files
process_files() {
    for file in "$@"; do
        if [ -f "$file" ]; then
            echo "Processing: $file"
            # Do something with the file
        else
            echo "Error: $file not found" >&2
        fi
    done
}

# Call with arguments
process_files "$@"

Option Parsing: Professional Scripts

For more complex scripts, use getopts:

bash
#!/bin/bash
# File: options.sh

# Default values
VERBOSE=false
OUTPUT_FILE=""
INPUT_FILES=()

# Parse options
while getopts ":vf:o:" opt; do
    case $opt in
        v)
            VERBOSE=true
            ;;
        f)
            INPUT_FILES+=("$OPTARG")
            ;;
        o)
            OUTPUT_FILE="$OPTARG"
            ;;
        \?)
            echo "Invalid option: -$OPTARG" >&2
            exit 1
            ;;
        :)
            echo "Option -$OPTARG requires an argument." >&2
            exit 1
            ;;
    esac
done

# Shift processed options
shift $((OPTIND-1))

# Remaining arguments
OTHER_ARGS=("$@")

# Show what we got
if $VERBOSE; then
    echo "Verbose mode enabled"
fi

if [ -n "$OUTPUT_FILE" ]; then
    echo "Output file: $OUTPUT_FILE"
fi

echo "Input files: ${INPUT_FILES[@]}"
echo "Other arguments: ${OTHER_ARGS[@]}"

# Usage: ./options.sh -v -f file1.txt -f file2.txt -o output.txt arg1 arg2

🚨 Error Handling: Making Scripts Robust

Exit Codes: Success or Failure?

Every command returns an exit code (0 = success, non-zero = failure).

bash
#!/bin/bash
# File: exit-codes.sh

# Check exit code of last command
ls /tmp
echo "Exit code: $?"

ls /nonexistent 2>/dev/null
echo "Exit code: $?"  # Non-zero means error

# Use exit codes in conditions
if ls /tmp >/dev/null 2>&1; then
    echo "/tmp exists"
else
    echo "/tmp doesn't exist"
fi

# Set your script's exit code
if [ ! -f "/etc/passwd" ]; then
    echo "Critical file missing!" >&2
    exit 1  # Exit with error
fi

echo "Everything is fine"
exit 0  # Exit with success

Trapping Errors and Signals

bash
#!/bin/bash
# File: error-handling.sh

# Trap errors
set -e  # Exit immediately if any command fails
# set -u  # Exit if using undefined variable
# set -o pipefail  # Fail if any command in pipeline fails

# Trap specific signals
cleanup() {
    echo "Cleaning up..."
    rm -f /tmp/tempfile.$$
    echo "Cleanup complete"
}

trap cleanup EXIT  # Run cleanup on exit
trap 'echo "Interrupted!"; exit 1' INT TERM  # Handle Ctrl+C

# Example with error handling
backup_file() {
    local source=$1
    local destination=$2
    
    if [ ! -f "$source" ]; then
        echo "Error: Source file not found: $source" >&2
        return 1
    fi
    
    if ! cp "$source" "$destination"; then
        echo "Error: Failed to copy $source to $destination" >&2
        return 1
    fi
    
    echo "Backup successful: $source -> $destination"
    return 0
}

# Try the backup
if backup_file "/etc/passwd" "/tmp/backup.passwd"; then
    echo "Backup succeeded"
else
    echo "Backup failed" >&2
    exit 1
fi

# Create temp file
echo "Creating temp file..."
echo "temp data" > /tmp/tempfile.$$

# This will trigger the cleanup on exit

Logging: Keeping Records

bash
#!/bin/bash
# File: logging.sh

LOG_FILE="/var/log/myscript.log"
DEBUG=false

# Logging function
log() {
    local level=$1
    local message=$2
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
    
    # Also print to console if debug mode
    if [ "$level" = "ERROR" ] || [ "$DEBUG" = true ]; then
        echo "[$level] $message"
    fi
}

# Usage
log "INFO" "Script started"
log "DEBUG" "Debug information"
log "WARNING" "Something might be wrong"
log "ERROR" "Something went wrong!"

# Example with error checking
check_disk_space() {
    local usage=$(df / | awk 'NR==2{print $5}' | sed 's/%//')
    
    if [ $usage -gt 90 ]; then
        log "ERROR" "Disk usage critical: ${usage}%"
        return 1
    elif [ $usage -gt 80 ]; then
        log "WARNING" "Disk usage high: ${usage}%"
    else
        log "INFO" "Disk usage normal: ${usage}%"
    fi
    return 0
}

check_disk_space

⚙️ Automating System Tasks

System Information Script

bash
#!/bin/bash
# File: system-info.sh

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo -e "${GREEN}=== System Information ===${NC}"
echo

# Host information
echo -e "${YELLOW}Host Information:${NC}"
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "Uptime: $(uptime -p | sed 's/up //')"

# CPU information
echo -e "\n${YELLOW}CPU Information:${NC}"
echo "CPU Model: $(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2 | xargs)"
echo "CPU Cores: $(nproc)"
echo "Load Average: $(uptime | awk -F'load average:' '{print $2}' | xargs)"

# Memory information
echo -e "\n${YELLOW}Memory Information:${NC}"
TOTAL_MEM=$(free -h | awk '/^Mem:/ {print $2}')
USED_MEM=$(free -h | awk '/^Mem:/ {print $3}')
PERCENT=$(free | awk '/^Mem:/ {printf("%.0f"), $3/$2*100}')
echo "Total: $TOTAL_MEM"
echo "Used: $USED_MEM"
echo "Usage: $PERCENT%"

# Color code memory usage
if [ $PERCENT -gt 90 ]; then
    echo -e "Status: ${RED}CRITICAL${NC}"
elif [ $PERCENT -gt 70 ]; then
    echo -e "Status: ${YELLOW}WARNING${NC}"
else
    echo -e "Status: ${GREEN}OK${NC}"
fi

# Disk information
echo -e "\n${YELLOW}Disk Information:${NC}"
df -h / | awk 'NR==2 {printf("Mount: %s\nTotal: %s\nUsed: %s\nFree: %s\nUsage: %s\n", 
    $6, $2, $3, $4, $5)}'

# Network information
echo -e "\n${YELLOW}Network Information:${NC}"
IP_ADDR=$(hostname -I | awk '{print $1}')
PUBLIC_IP=$(curl -s ifconfig.me 2>/dev/null || echo "Not available")
echo "Local IP: $IP_ADDR"
echo "Public IP: $PUBLIC_IP"

# Services status
echo -e "\n${YELLOW}Services Status:${NC}"
for service in ssh nginx mysql docker; do
    if systemctl is-active --quiet $service 2>/dev/null; then
        echo -e "$service: ${GREEN}Running${NC}"
    else
        echo -e "$service: ${RED}Stopped${NC}"
    fi
done

echo -e "\n${GREEN}Report generated at: $(date)${NC}"

User Management Automation

bash
#!/bin/bash
# File: user-manager.sh

# Configuration
USER_LIST_FILE="users.txt"
DEFAULT_SHELL="/bin/bash"
DEFAULT_GROUP="developers"

# Check if running as root
if [ "$(id -u)" -ne 0 ]; then
    echo "This script must be run as root" >&2
    exit 1
fi

# Function to create a user
create_user() {
    local username=$1
    local fullname=$2
    
    # Check if user exists
    if id "$username" &>/dev/null; then
        echo "User $username already exists, skipping..."
        return 1
    fi
    
    # Create user with home directory
    useradd -m -s "$DEFAULT_SHELL" -c "$fullname" "$username"
    
    if [ $? -eq 0 ]; then
        echo "User $username created successfully"
        
        # Set random password
        password=$(openssl rand -base64 12)
        echo "$username:$password" | chpasswd
        
        # Force password change on first login
        chage -d 0 "$username"
        
        # Add to default group
        usermod -aG "$DEFAULT_GROUP" "$username"
        
        # Create .ssh directory
        mkdir -p "/home/$username/.ssh"
        chmod 700 "/home/$username/.ssh"
        chown "$username:$username" "/home/$username/.ssh"
        
        # Save credentials (in real script, send via secure method)
        echo "Username: $username" >> /tmp/user_credentials.txt
        echo "Password: $password" >> /tmp/user_credentials.txt
        echo "---" >> /tmp/user_credentials.txt
        
        return 0
    else
        echo "Failed to create user $username" >&2
        return 1
    fi
}

# Main script
echo "Starting user creation..."

# Check if user list file exists
if [ ! -f "$USER_LIST_FILE" ]; then
    echo "Error: $USER_LIST_FILE not found" >&2
    echo "Create a file with format: username:Full Name"
    exit 1
fi

# Read file and create users
while IFS=':' read -r username fullname; do
    # Skip empty lines and comments
    [[ -z "$username" || "$username" =~ ^# ]] && continue
    
    echo "Creating user: $username ($fullname)"
    
    if create_user "$username" "$fullname"; then
        echo "✓ Success"
    else
        echo "✗ Failed"
    fi
    echo
done < "$USER_LIST_FILE"

echo "User creation complete!"
echo "Credentials saved to: /tmp/user_credentials.txt"
echo "Please distribute credentials securely and delete the file."

⏰ Script Scheduling with Cron

Understanding Cron

Cron is Linux's task scheduler. It runs commands at specific times automatically. Think of it as an alarm clock for your scripts.

Cron Syntax

text
* * * * * command_to_execute
│ │ │ │ │
│ │ │ │ └── Day of week (0-7, 0=Sunday)
│ │ │ └──── Month (1-12)
│ │ └────── Day of month (1-31)
│ └──────── Hour (0-23)
└────────── Minute (0-59)

Managing Cron Jobs

bash
# Edit your user's cron jobs
crontab -e

# List your cron jobs
crontab -l

# Remove all your cron jobs
crontab -r

# Edit another user's cron jobs (as root)
sudo crontab -u username -e

# System-wide cron directories
ls /etc/cron.hourly/
ls /etc/cron.daily/
ls /etc/cron.weekly/
ls /etc/cron.monthly/

Common Cron Examples

bash
# In crontab -e:

# Run every minute
* * * * * /path/to/script.sh

# Run at 2:30 AM every day
30 2 * * * /backup/daily-backup.sh

# Run every Monday at 4 PM
0 16 * * 1 /reports/weekly-report.sh

# Run every 5 minutes
*/5 * * * * /monitoring/check-status.sh

# Run at 10:15 on the 1st of each month
15 10 1 * * /billing/monthly-invoice.sh

# Run every hour from 9 AM to 5 PM, Monday-Friday
0 9-17 * * 1-5 /monitoring/hourly-check.sh

# Run at reboot
@reboot /scripts/startup.sh

# Special strings
@yearly    /scripts/yearly-maintenance.sh    # Same as 0 0 1 1 *
@monthly   /scripts/monthly-backup.sh       # Same as 0 0 1 * *
@weekly    /scripts/weekly-cleanup.sh       # Same as 0 0 * * 0
@daily     /scripts/daily-report.sh         # Same as 0 0 * * *
@hourly    /scripts/hourly-monitor.sh       # Same as 0 * * * *

Cron Best Practices

bash
# 1. Always use full paths
# BAD: script.sh
# GOOD: /home/user/scripts/script.sh

# 2. Redirect output to logs
* * * * * /script.sh >> /var/log/script.log 2>&1

# 3. Set proper environment
* * * * * cd /path && ./script.sh

# 4. Use locking to prevent overlapping runs
* * * * * /usr/bin/flock -n /tmp/script.lock /script.sh

# 5. Test commands manually first

# Example with all best practices:
*/5 * * * * cd /opt/app && /usr/bin/flock -n /tmp/backup.lock /opt/app/backup.sh >> /var/log/backup.log 2>&1

🎯 Real-world Examples

Example 1: Automated Backup Script

bash
#!/bin/bash
# File: backup-system.sh
# Automated backup script with rotation and notifications

# Configuration
BACKUP_DIR="/backup"
SOURCE_DIRS="/home /etc /var/www"
DB_NAME="mydatabase"
DB_USER="root"
DB_PASS="password"
RETENTION_DAYS=7
LOG_FILE="/var/log/backup.log"
EMAIL="admin@example.com"

# Log function
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Error handling
set -e
trap 'log "ERROR: Backup failed!"; exit 1' ERR

# Check if backup directory exists
mkdir -p "$BACKUP_DIR"

# Start backup
log "Starting backup..."

# Create timestamp
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
BACKUP_PATH="$BACKUP_DIR/backup_$TIMESTAMP"

# Create backup directory
mkdir -p "$BACKUP_PATH"

# Backup files
log "Backing up files..."
for dir in $SOURCE_DIRS; do
    if [ -d "$dir" ]; then
        log "  Backing up $dir"
        tar -czf "$BACKUP_PATH/$(basename $dir).tar.gz" "$dir" 2>/dev/null || log "  Warning: Failed to backup $dir"
    fi
done

# Backup database
log "Backing up database..."
mysqldump -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BACKUP_PATH/database.sql" 2>/dev/null || log "  Warning: Database backup failed"

# Create backup manifest
log "Creating manifest..."
ls -lh "$BACKUP_PATH" > "$BACKUP_PATH/manifest.txt"
du -sh "$BACKUP_PATH" >> "$BACKUP_PATH/manifest.txt"

# Compress backup
log "Compressing backup..."
tar -czf "$BACKUP_PATH.tar.gz" -C "$BACKUP_DIR" "backup_$TIMESTAMP"
rm -rf "$BACKUP_PATH"

# Clean old backups
log "Cleaning old backups..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete

# Calculate backup size
BACKUP_SIZE=$(du -h "$BACKUP_PATH.tar.gz" | cut -f1)

# Send notification
log "Backup completed successfully!"
log "Backup size: $BACKUP_SIZE"
log "Location: $BACKUP_PATH.tar.gz"

# Send email (if mail command is configured)
echo "Backup completed at $(date)" | mail -s "Backup Successful" "$EMAIL"

exit 0

Example 2: System Monitoring Script

bash
#!/bin/bash
# File: system-monitor.sh
# Monitors system resources and sends alerts

# Thresholds
CPU_THRESHOLD=80
MEM_THRESHOLD=85
DISK_THRESHOLD=90
LOAD_THRESHOLD=$(nproc)  # Number of CPU cores

# Alert file (prevents duplicate alerts)
ALERT_FILE="/tmp/monitor.alerts"
LOG_FILE="/var/log/system-monitor.log"

# Initialize alert file
touch "$ALERT_FILE"

# Log function
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Check CPU usage
check_cpu() {
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    local load=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | tr -d ',')
    
    log "CPU Usage: ${cpu_usage}%, Load: $load"
    
    if (( $(echo "$cpu_usage > $CPU_THRESHOLD" | bc -l) )); then
        send_alert "CPU" "CPU usage is ${cpu_usage}% (threshold: ${CPU_THRESHOLD}%)"
    fi
    
    if (( $(echo "$load > $LOAD_THRESHOLD" | bc -l) )); then
        send_alert "LOAD" "Load average is $load (threshold: ${LOAD_THRESHOLD})"
    fi
}

# Check memory usage
check_memory() {
    local mem_usage=$(free | awk '/Mem/{printf("%.0f"), $3/$2*100}')
    local swap_usage=$(free | awk '/Swap/{printf("%.0f"), $3/$2*100}')
    
    log "Memory Usage: ${mem_usage}%, Swap: ${swap_usage}%"
    
    if [ $mem_usage -gt $MEM_THRESHOLD ]; then
        send_alert "MEMORY" "Memory usage is ${mem_usage}% (threshold: ${MEM_THRESHOLD}%)"
    fi
    
    if [ $swap_usage -gt 50 ]; then
        send_alert "SWAP" "High swap usage: ${swap_usage}%"
    fi
}

# Check disk usage
check_disk() {
    local disk_usage=$(df / | awk 'NR==2{print $5}' | sed 's/%//')
    local inode_usage=$(df -i / | awk 'NR==2{print $5}' | sed 's/%//')
    
    log "Disk Usage: ${disk_usage}%, Inodes: ${inode_usage}%"
    
    if [ $disk_usage -gt $DISK_THRESHOLD ]; then
        send_alert "DISK" "Disk usage is ${disk_usage}% (threshold: ${DISK_THRESHOLD}%)"
    fi
    
    if [ $inode_usage -gt 90 ]; then
        send_alert "INODE" "Inode usage is ${inode_usage}%"
    fi
}

# Check services
check_services() {
    local services="ssh nginx mysql docker"
    
    for service in $services; do
        if systemctl is-active --quiet "$service"; then
            log "Service $service: RUNNING"
        else
            send_alert "SERVICE" "Service $service is DOWN"
        fi
    done
}

# Send alert (prevents duplicates)
send_alert() {
    local type=$1
    local message=$2
    local alert_key="${type}_${message}"
    
    # Check if already alerted
    if ! grep -q "$alert_key" "$ALERT_FILE"; then
        log "ALERT: $message"
        echo "$alert_key" >> "$ALERT_FILE"
        
        # Send email alert
        echo "Alert: $message at $(date)" | mail -s "System Alert: $type" "admin@example.com"
        
        # Could also send to Slack/Teams/etc.
        # curl -X POST -H 'Content-type: application/json' \
        # --data "{\"text\":\"$message\"}" \
        # https://hooks.slack.com/services/...
    fi
}

# Clear old alerts (older than 1 hour)
clear_old_alerts() {
    if [ -f "$ALERT_FILE" ]; then
        # In real implementation, you'd track timestamps
        # For simplicity, we clear if file is old
        if [ $(find "$ALERT_FILE" -mmin +60) ]; then
            > "$ALERT_FILE"
        fi
    fi
}

# Main monitoring function
main() {
    log "=== Starting system monitor ==="
    
    clear_old_alerts
    check_cpu
    check_memory
    check_disk
    check_services
    
    log "=== Monitor completed ==="
    echo
}

# Run main function
main

Example 3: Log Cleanup Automation

bash
#!/bin/bash
# File: log-cleanup.sh
# Automatically cleans up old logs and archives important ones

# Configuration
LOG_DIRS="/var/log /var/log/nginx /var/log/mysql /opt/app/logs"
ARCHIVE_DIR="/backup/logs"
RETENTION_DAYS=30
COMPRESS_DAYS=7
MAX_LOG_SIZE="100M"
LOG_FILE="/var/log/log-cleanup.log"

# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# Log function
log() {
    local level=$1
    local message=$2
    local color=$NC
    
    case $level in
        INFO) color=$GREEN ;;
        WARN) color=$YELLOW ;;
        ERROR) color=$RED ;;
    esac
    
    echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] ${color}[$level]${NC} $message" | tee -a "$LOG_FILE"
}

# Create archive directory
mkdir -p "$ARCHIVE_DIR"

# Main cleanup function
cleanup_logs() {
    log "INFO" "Starting log cleanup..."
    
    total_saved=0
    
    for log_dir in $LOG_DIRS; do
        if [ ! -d "$log_dir" ]; then
            log "WARN" "Directory not found: $log_dir"
            continue
        fi
        
        log "INFO" "Processing: $log_dir"
        
        # Archive logs older than COMPRESS_DAYS
        find "$log_dir" -name "*.log" -type f -mtime +$COMPRESS_DAYS 2>/dev/null | while read logfile; do
            if [ -s "$logfile" ]; then
                filename=$(basename "$logfile")
                archive_name="${filename}.$(date '+%Y%m%d').tar.gz"
                
                log "INFO" "  Archiving: $filename"
                
                # Archive the log
                tar -czf "$ARCHIVE_DIR/$archive_name" -C "$(dirname "$logfile")" "$filename"
                
                # Clear the original log (keep last 1000 lines)
                tail -1000 "$logfile" > "${logfile}.tmp"
                mv "${logfile}.tmp" "$logfile"
                
                saved=$(stat -c%s "$ARCHIVE_DIR/$archive_name")
                total_saved=$((total_saved + saved))
            fi
        done
        
        # Delete archived logs older than RETENTION_DAYS
        deleted_count=$(find "$ARCHIVE_DIR" -name "*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete -print | wc -l)
        if [ $deleted_count -gt 0 ]; then
            log "INFO" "  Deleted $deleted_count old archives"
        fi
        
        # Truncate large log files
        find "$log_dir" -name "*.log" -type f -size +$MAX_LOG_SIZE 2>/dev/null | while read logfile; do
            log "WARN" "  Truncating large log: $(basename "$logfile")"
            tail -10000 "$logfile" > "${logfile}.tmp"
            mv "${logfile}.tmp" "$logfile"
        done
    done
    
    # Cleanup empty files
    find $LOG_DIRS -name "*.log" -type f -empty -delete 2>/dev/null | wc -l | read empty_deleted
    if [ $empty_deleted -gt 0 ]; then
        log "INFO" "Deleted $empty_deleted empty log files"
    fi
    
    # Calculate space saved in human readable format
    if [ $total_saved -gt 0 ]; then
        saved_human=$(numfmt --to=iec --suffix=B $total_saved)
        log "INFO" "Total space saved: $saved_human"
    fi
    
    log "INFO" "Log cleanup completed!"
}

# Run with error handling
set -e
trap 'log "ERROR" "Log cleanup failed!"; exit 1' ERR

cleanup_logs

📋 Quick Reference Cheat Sheet

TaskCode ExampleDescription
Shebang#!/bin/bashFirst line of script
VariableNAME="Alice"Define variable
Use variableecho "$NAME"Print variable
Command outputDATE=$(date)Store command output
User inputread -p "Name: " NAMEGet user input
If conditionif [ $A -gt $B ]; thenCompare numbers
If file existsif [ -f "$FILE" ]; thenCheck if file exists
For loopfor i in {1..5}; doLoop through range
While loopwhile [ $COUNT -gt 0 ]; doLoop while true
Functionmyfunc() { echo "Hello"; }Define function
Function callmyfuncCall function
Arguments$1, $2, $@Script arguments
Exit codeexit 0 or exit 1Exit script
Error checkif command; thenCheck if command succeeded
Loggingecho "msg" >> logfileAppend to log
Cron job* * * * * /script.shRun every minute
Cron with log* * * * * /script.sh >> log 2>&1Redirect output

🚀 Practice Exercises

Exercise 1: Create a System Health Dashboard

bash
#!/bin/bash
# health-dashboard.sh

# Function to print section header
header() {
    echo "========================================"
    echo "    $1"
    echo "========================================"
}

# System info
header "System Information"
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "Uptime: $(uptime -p)"

# CPU
header "CPU Information"
echo "Cores: $(nproc)"
echo "Load: $(uptime | awk -F'load average:' '{print $2}')"

# Memory
header "Memory Information"
free -h | awk '/^Mem:/ {print "Total: "$2, "Used: "$3, "Free: "$4, "Usage: "$3/$2*100"%" }'

# Disk
header "Disk Information"
df -h / | awk 'NR==2 {print "Total: "$2, "Used: "$3, "Free: "$4, "Usage: "$5}'

# Services
header "Services Status"
services=("ssh" "nginx" "mysql" "docker")
for service in "${services[@]}"; do
    if systemctl is-active --quiet $service; then
        echo "✓ $service: Running"
    else
        echo "✗ $service: Stopped"
    fi
done

Exercise 2: File Organizer Script

bash
#!/bin/bash
# organize-files.sh

TARGET_DIR="$1"

if [ -z "$TARGET_DIR" ]; then
    echo "Usage: $0 <directory>"
    exit 1
fi

if [ ! -d "$TARGET_DIR" ]; then
    echo "Error: Directory not found"
    exit 1
fi

cd "$TARGET_DIR"

# Create category directories
mkdir -p images documents archives scripts others

# Move files by extension
for file in *; do
    if [ -f "$file" ]; then
        case "${file##*.}" in
            jpg|jpeg|png|gif|bmp)
                mv "$file" images/
                echo "Moved $file to images/"
                ;;
            pdf|doc|docx|txt|md)
                mv "$file" documents/
                echo "Moved $file to documents/"
                ;;
            zip|tar|gz|bz2)
                mv "$file" archives/
                echo "Moved $file to archives/"
                ;;
            sh|py|js|php)
                mv "$file" scripts/
                echo "Moved $file to scripts/"
                ;;
            *)
                mv "$file" others/
                echo "Moved $file to others/"
                ;;
        esac
    fi
done

echo "Organization complete!"

Exercise 3: Password Generator

bash
#!/bin/bash
# password-generator.sh

# Default length
LENGTH=12

# Parse arguments
while getopts "l:h" opt; do
    case $opt in
        l) LENGTH=$OPTARG ;;
        h) echo "Usage: $0 [-l length]"; exit 0 ;;
        *) echo "Invalid option"; exit 1 ;;
    esac
done

# Character sets
UPPER="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LOWER="abcdefghijklmnopqrstuvwxyz"
DIGITS="0123456789"
SPECIAL="!@#$%^&*()_+-=[]{}|;:,.<>?"

# Combine all characters
ALL_CHARS="${UPPER}${LOWER}${DIGITS}${SPECIAL}"

echo "Generating $LENGTH character password..."

# Generate password
PASSWORD=""
for i in $(seq 1 $LENGTH); do
    # Get random character
    CHAR=${ALL_CHARS:$((RANDOM % ${#ALL_CHARS})):1}
    PASSWORD="${PASSWORD}${CHAR}"
done

echo "Password: $PASSWORD"

# Strength check
STRENGTH=0
[[ $PASSWORD =~ [A-Z] ]] && ((STRENGTH++))
[[ $PASSWORD =~ [a-z] ]] && ((STRENGTH++))
[[ $PASSWORD =~ [0-9] ]] && ((STRENGTH++))
[[ $PASSWORD =~ [!@#\$%^\&*()_+-=[]{}|;:,.<>?] ]] && ((STRENGTH++))

echo -n "Strength: "
case $STRENGTH in
    4) echo "Strong" ;;
    3) echo "Good" ;;
    2) echo "Weak" ;;
    *) echo "Very Weak" ;;
esac

🔗 Master Shell Scripting with Hands-on Labs

Shell scripting is the most practical skill for DevOps automation. The best way to learn is through hands-on practice with real-world scenarios.

👉 Practice shell scripting with guided exercises and real projects at:
https://devops.trainwithsky.com/

Our platform provides:

  • Step-by-step scripting tutorials

  • Real automation challenges

  • Code review and feedback

  • Production-ready script templates

  • Progress from beginner to advanced scripting


Frequently Asked Questions

Q: Should I use Bash or Python for scripting?
A: Use Bash for system tasks (file operations, process management). Use Python for complex logic, data processing, or when you need libraries.

Q: How do I debug a shell script?
A: Use set -x to see each command executed, or add echo statements to trace execution.

Q: What's the difference between $VAR and ${VAR}?
A: They're usually the same. Use ${VAR} when concatenating: echo "${VAR}text" vs echo "$VARtext" (which looks for variable VARtext).

Q: How do I make my script run faster?
A: Avoid unnecessary subshells $(command), use built-in shell features, and minimize file operations in loops.

Q: Should I always use #!/bin/bash?
A: Yes for Bash scripts. For maximum compatibility, use #!/usr/bin/env bash.

Q: How do I handle spaces in filenames?
A: Always quote variables: "$filename". Use find -print0 | xargs -0 for commands that don't handle spaces well.

Q: What's the best way to learn shell scripting?
A: 1) Start with small scripts 2) Read others' scripts 3) Practice daily 4) Automate your own tasks.

Have scripting questions or need help with a script? Share your code in the comments below! 💬


Comments

Popular posts from this blog

Introduction to Terraform – The Future of Infrastructure as Code

  Introduction to Terraform – The Future of Infrastructure as Code In today’s fast-paced DevOps world, managing infrastructure manually is outdated . This is where Terraform comes in—a powerful Infrastructure as Code (IaC) tool that allows you to define, provision, and manage cloud infrastructure efficiently . Whether you're working with AWS, Azure, Google Cloud, or on-premises servers , Terraform provides a declarative, automation-first approach to infrastructure deployment. Shape Your Future with AI & Infinite Knowledge...!! Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! In today’s digital-first world, agility and automation are no longer optional—they’re essential. Companies across the globe are rapidly shifting their operations to the cloud to keep up with the pace of innovatio...

📊 Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd

  Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd Monitoring and logging are essential for maintaining a healthy and well-performing Kubernetes cluster. In this guide, we’ll cover why monitoring is important, key monitoring tools like Prometheus and Grafana, and logging tools like Fluentd to help you gain visibility into your cluster’s performance and logs. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction In today’s fast-paced cloud-native environment, Kubernetes has emerged as the de-facto container orchestration platform. But deploying and managing applications in Kubernetes is just half the ba...

🔒 Kubernetes Security – RBAC, Network Policies, and Secrets Management

  Kubernetes Security – RBAC, Network Policies, and Secrets Management Security is a critical aspect of managing Kubernetes clusters. In this guide, we'll cover essential security mechanisms like Role-Based Access Control (RBAC) , Network Policies , and Secrets Management to help you secure your Kubernetes environment effectively. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction: Why Kubernetes Security Is Non-Negotiable As Kubernetes becomes the backbone of modern cloud-native infrastructure, security is no longer optional—it’s mission-critical . With multiple moving parts like containers, pods, services, nodes, and more, Kuberne...