Advanced Linux Shell Scripting

Advanced Linux Shell Scripting

Advanced shell scripting takes you beyond basic commands and into the realm of automation, system administration, and powerful data processing. This guide will help you write sophisticated scripts that can handle complex tasks efficiently.

If you’re new to shell scripting, you might want to start with our shell scripting basics guide first.

Functions and Modular Scripting

Functions make your scripts reusable, organized, and easier to maintain.

Creating and Using Functions

#!/bin/bash

# Function definition
log_message() {
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local level=$1
    local message=$2
    echo "[$timestamp] [$level] $message" | tee -a /var/log/my_script.log
}

# Function with return value
check_file_exists() {
    local file_path=$1
    if [[ -f "$file_path" ]]; then
        return 0  # Success
    else
        return 1  # Failure
    fi
}

# Function with parameters
backup_file() {
    local source_file=$1
    local backup_dir=${2:-"/tmp/backups"}  # Default backup directory
    
    # Create backup directory if it doesn't exist
    mkdir -p "$backup_dir"
    
    # Create backup with timestamp
    local timestamp=$(date '+%Y%m%d_%H%M%S')
    local backup_file="${backup_dir}/$(basename "$source_file")_$timestamp"
    
    if cp "$source_file" "$backup_file"; then
        log_message "INFO" "Backup created: $backup_file"
        return 0
    else
        log_message "ERROR" "Failed to backup $source_file"
        return 1
    fi
}

# Usage
log_message "INFO" "Script started"

if check_file_exists "/etc/passwd"; then
    log_message "INFO" "System files exist"
else
    log_message "ERROR" "System files missing"
fi

backup_file "/etc/hosts" "/home/user/backups"

Advanced Function Techniques

#!/bin/bash

# Function with variable arguments
process_files() {
    local count=0
    
    # Iterate over all arguments
    for file in "$@"; do
        if [[ -f "$file" ]]; then
            echo "Processing: $file"
            # Perform operations on file
            ((count++))
        else
            echo "Warning: $file not found" >&2
        fi
    done
    
    echo "Processed $count files"
}

# Function with local and global variables
counter=0

increment_counter() {
    local increment=${1:-1}  # Default increment of 1
    counter=$((counter + increment))
    echo "Counter is now: $counter"
}

# Function that returns complex data
get_system_info() {
    local info=""
    info+="Hostname: $(hostname)\n"
    info+="Kernel: $(uname -r)\n"
    info+="Uptime: $(uptime -p)\n"
    info+="Memory Usage: $(free -h | grep '^Mem:' | awk '{print $3 "/" $2}')\n"
    
    echo -e "$info"
}

# Function libraries
source_library() {
    local lib_file=$1
    if [[ -f "$lib_file" ]]; then
        source "$lib_file"
    else
        echo "Library file $lib_file not found" >&2
        return 1
    fi
}

Advanced Text Processing

Master text processing tools like awk, sed, and grep for powerful data manipulation.

Advanced Awk Usage

#!/bin/bash

# Process log files with awk
analyze_web_log() {
    local log_file=$1
    
    echo "=== Web Log Analysis ==="
    
    # Count requests by hour
    awk '{print $4}' "$log_file" | \
    sed 's/\[//' | cut -d: -f1 | \
    sort | uniq -c | sort -nr
    
    # Top 10 IP addresses
    echo -e "\n=== Top 10 IP Addresses ==="
    awk '{print $1}' "$log_file" | \
    sort | uniq -c | sort -nr | head -10
    
    # HTTP status code distribution
    echo -e "\n=== HTTP Status Codes ==="
    awk '{print $9}' "$log_file" | \
    sort | uniq -c | sort -nr
    
    # Calculate average response size
    echo -e "\n=== Average Response Size ==="
    awk '{sum+=$10; count++} END {print "Average:", sum/count " bytes"}' "$log_file"
}

# Complex data transformation
transform_csv() {
    local input_file=$1
    local output_file=$2
    
    awk -F',' 'BEGIN {OFS=","}
    NR == 1 {
        # Print header
        print "ID", "Name", "Score", "Grade"
        next
    }
    NR > 1 {
        # Calculate grade based on score
        grade = ($3 >= 90) ? "A" : ($3 >= 80) ? "B" : ($3 >= 70) ? "C" : "F"
        
        # Clean up name (remove extra spaces)
        gsub(/^[ \t]+|[ \t]+$/, "", $2)
        
        print $1, $2, $3, grade
    }' "$input_file" > "$output_file"
}

# Multi-file processing
process_multiple_files() {
    local pattern=$1
    
    for file in $pattern; do
        if [[ -f "$file" ]]; then
            echo "Processing $file..."
            
            # Extract specific information
            awk -F':' '$3 >= 1000 && $3 <= 65535 {print $1 ":" $6}' /etc/passwd
            
            # Calculate statistics
            awk '{bytes+=$5; files++} END {
                print "Total files:", files
                print "Total bytes:", bytes
                print "Average:", bytes/files
            }' "$file"
        fi
    done
}

Advanced Sed Techniques

#!/bin/bash

# Batch text replacement
update_config_files() {
    local directory=$1
    local old_value=$2
    local new_value=$3
    
    find "$directory" -name "*.conf" -type f | while read file; do
        echo "Updating $file..."
        
        # Backup original file
        cp "$file" "$file.backup"
        
        # Perform multiple sed operations
        sed -i \
            -e "s/old_value/$new_value/g" \
            -e 's/^[[:space:]]*//' \
            -e '/^#/d' \
            -e '/^$/d' \
            "$file"
    done
}

# Log file processing
clean_log_file() {
    local log_file=$1
    local temp_file=$(mktemp)
    
    # Process log file with multiple sed commands
    sed -n \
        -e '1p' \
        -e '/ERROR/{
            s/^/\n/
            p
        }' \
        -e '/WARNING/w warnings.log' \
        -e '/CRITICAL/w critical.log' \
        "$log_file" > "$temp_file"
    
    mv "$temp_file" "$log_file"
}

# Format text files
format_text_file() {
    local input_file=$1
    local output_file=$2
    
    sed '
        # Remove leading whitespace
        s/^[[:space:]]*//
        
        # Remove trailing whitespace
        s/[[:space:]]*$//
        
        # Replace multiple spaces with single space
        s/[[:space:]]\+/ /g
        
        # Add line numbers
        = 
        # Combine line number with content
        N
        s/\n/ /
    ' "$input_file" > "$output_file"
}

Process Management and Monitoring

Advanced scripts often need to manage processes and monitor system resources.

Process Management Functions

#!/bin/bash

# Check if process is running
is_process_running() {
    local process_name=$1
    pgrep -f "$process_name" > /dev/null
    return $?
}

# Start process with PID tracking
start_daemon() {
    local command=$1
    local pid_file=$2
    
    if is_process_running "$command"; then
        echo "Process is already running"
        return 1
    fi
    
    # Start process in background
    nohup $command > /dev/null 2>&1 &
    local pid=$!
    
    # Save PID to file
    echo $pid > "$pid_file"
    echo "Started process with PID: $pid"
    
    return 0
}

# Stop process gracefully
stop_daemon() {
    local pid_file=$1
    
    if [[ ! -f "$pid_file" ]]; then
        echo "PID file not found"
        return 1
    fi
    
    local pid=$(cat "$pid_file")
    
    if kill -0 "$pid" 2>/dev/null; then
        echo "Stopping process $pid..."
        kill -TERM "$pid"
        
        # Wait for process to stop
        local timeout=30
        while kill -0 "$pid" 2>/dev/null && [[ $timeout -gt 0 ]]; do
            sleep 1
            ((timeout--))
        done
        
        # Force kill if still running
        if kill -0 "$pid" 2>/dev/null; then
            echo "Force killing process $pid..."
            kill -KILL "$pid"
        fi
        
        rm "$pid_file"
        echo "Process stopped"
    else
        echo "Process not running"
        rm -f "$pid_file"
    fi
}

# Monitor system resources
monitor_resources() {
    local threshold_cpu=${1:-80}
    local threshold_mem=${2:-80}
    local duration=${3:-60}
    local interval=${4:-5}
    
    echo "Monitoring system resources for $duration seconds..."
    
    local end_time=$(($(date +%s) + duration))
    
    while [[ $(date +%s) -lt $end_time ]]; do
        local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
        local mem_usage=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100.0}')
        
        echo "$(date '+%H:%M:%S') - CPU: ${cpu_usage}% MEM: ${mem_usage}%"
        
        # Alert if thresholds exceeded
        if (( $(echo "$cpu_usage > $threshold_cpu" | bc -l) )); then
            echo "WARNING: High CPU usage: ${cpu_usage}%"
        fi
        
        if [[ $mem_usage -gt $threshold_mem ]]; then
            echo "WARNING: High memory usage: ${mem_usage}%"
        fi
        
        sleep $interval
    done
}

Log Analysis and Monitoring

#!/bin/bash

# Analyze system logs
analyze_system_logs() {
    local log_file=${1:-"/var/log/syslog"}
    local time_range=${2:-"last hour"}
    
    echo "=== System Log Analysis ==="
    
    # Recent errors
    echo -e "\n=== Recent Errors ==="
    grep -i "error\|fail\|critical" "$log_file" | tail -10
    
    # Login attempts
    echo -e "\n=== Recent Login Attempts ==="
    grep -i "login\|ssh\|auth" "$log_file" | tail -10
    
    # Service status changes
    echo -e "\n=== Service Status Changes ==="
    grep -i "start\|stop\|restart" "$log_file" | tail -10
    
    # Resource warnings
    echo -e "\n=== Resource Warnings ==="
    grep -i "memory\|disk\|cpu" "$log_file" | tail -5
}

# Real-time log monitoring
monitor_logs() {
    local log_file=$1
    local pattern=$2
    
    if [[ -z "$pattern" ]]; then
        tail -f "$log_file"
    else
        tail -f "$log_file" | grep --line-buffered "$pattern"
    fi
}

# Generate system report
generate_system_report() {
    local report_file="system_report_$(date +%Y%m%d_%H%M%S).txt"
    
    {
        echo "=== System Report ==="
        echo "Generated: $(date)"
        echo ""
        
        echo "=== System Information ==="
        uname -a
        echo ""
        
        echo "=== Disk Usage ==="
        df -h
        echo ""
        
        echo "=== Memory Usage ==="
        free -h
        echo ""
        
        echo "=== Top Processes ==="
        ps aux --sort=-%cpu | head -10
        echo ""
        
        echo "=== Network Connections ==="
        netstat -tuln | head -10
        echo ""
        
        echo "=== System Load ==="
        uptime
        echo ""
        
        echo "=== Recent Errors ==="
        tail -20 /var/log/syslog | grep -i error
        
    } > "$report_file"
    
    echo "Report saved to: $report_file"
}

Advanced File Operations

Complex file operations including batch processing, archiving, and synchronization.

File System Operations

#!/bin/bash

# Batch file operations
batch_process_files() {
    local source_dir=$1
    local operation=$2
    
    case $operation in
        "rename")
            # Add timestamp to all files
            find "$source_dir" -type f | while read file; do
                local dir=$(dirname "$file")
                local name=$(basename "$file")
                local ext="${name##*.}"
                local base="${name%.*}"
                local timestamp=$(date +%Y%m%d_%H%M%S)
                
                mv "$file" "$dir/${base}_${timestamp}.${ext}"
            done
            ;;
        "compress")
            # Compress all log files
            find "$source_dir" -name "*.log" -type f -exec gzip {} \;
            ;;
        "cleanup")
            # Remove files older than 30 days
            find "$source_dir" -type f -mtime +30 -delete
            ;;
        "organize")
            # Organize files by extension
            find "$source_dir" -type f | while read file; do
                local ext="${file##*.}"
                local target_dir="$source_dir/$ext"
                
                mkdir -p "$target_dir"
                mv "$file" "$target_dir/"
            done
            ;;
    esac
}

# Synchronize directories
sync_directories() {
    local source=$1
    local destination=$2
    
    # Create destination if it doesn't exist
    mkdir -p "$destination"
    
    # Use rsync for efficient synchronization
    rsync -av --delete --progress "$source/" "$destination/"
    
    # Create sync log
    echo "Sync completed at $(date)" >> "$destination/sync.log"
}

# Advanced backup system
advanced_backup() {
    local source_dir=$1
    local backup_dir=$2
    local backup_type=${3:-"incremental"}
    
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_path="$backup_dir/backup_$timestamp"
    
    case $backup_type in
        "full")
            # Full backup
            tar -czf "$backup_path.tar.gz" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
            ;;
        "incremental")
            # Incremental backup (requires previous backup)
            local last_backup=$(ls -t "$backup_dir"/backup_*.tar.gz | head -1)
            if [[ -n "$last_backup" ]]; then
                tar -czf "$backup_path.tar.gz" --newer "$last_backup" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
            else
                tar -czf "$backup_path.tar.gz" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
            fi
            ;;
        "differential")
            # Differential backup (since last full backup)
            local last_full=$(ls -t "$backup_dir"/backup_*.tar.gz | grep -v incremental | head -1)
            if [[ -n "$last_full" ]]; then
                tar -czf "$backup_path.tar.gz" --newer "$last_full" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
            else
                tar -czf "$backup_path.tar.gz" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
            fi
            ;;
    esac
    
    echo "Backup completed: $backup_path.tar.gz"
}

Error Handling and Validation

Robust error handling makes your scripts reliable and user-friendly.

Comprehensive Error Handling

#!/bin/bash

# Error handling setup
set -euo pipefail  # Exit on error, undefined variables, and pipe failures
IFS=$'\n\t'        # Set Internal Field Separator

# Custom error handler
error_handler() {
    local line_number=$1
    local exit_code=$2
    
    echo "Error occurred in script at line $line_number (exit code: $exit_code)" >&2
    echo "Command failed: ${BASH_COMMAND}" >&2
    
    # Cleanup actions
    cleanup_on_error
    
    exit $exit_code
}

# Set up error trap
trap 'error_handler ${LINENO} $?' ERR

# Cleanup function
cleanup_on_error() {
    echo "Performing cleanup..."
    # Remove temporary files
    rm -f /tmp/script_$$_*
    # Kill background processes
    jobs -p | xargs -r kill
}

# Input validation
validate_input() {
    local input=$1
    local type=$2
    
    case $type in
        "email")
            [[ "$input" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
            ;;
        "number")
            [[ "$input" =~ ^[0-9]+$ ]]
            ;;
        "directory")
            [[ -d "$input" ]]
            ;;
        "file")
            [[ -f "$input" ]]
            ;;
        *)
            echo "Unknown validation type: $type" >&2
            return 1
            ;;
    esac
}

# Safe file operations
safe_file_operation() {
    local operation=$1
    local file=$2
    
    # Validate file path
    if [[ "$file" =~ \.\. ]]; then
        echo "Error: Path traversal attempt detected" >&2
        return 1
    fi
    
    # Ensure file is within allowed directory
    local allowed_dir="/home/user/data"
    if [[ ! "$file" == "$allowed_dir"* ]]; then
        echo "Error: File outside allowed directory" >&2
        return 1
    fi
    
    case $operation in
        "read")
            if [[ -r "$file" ]]; then
                cat "$file"
            else
                echo "Error: File not readable" >&2
                return 1
            fi
            ;;
        "write")
            if [[ -w "$(dirname "$file")" ]]; then
                # Create backup before writing
                if [[ -f "$file" ]]; then
                    cp "$file" "$file.backup"
                fi
                return 0
            else
                echo "Error: Directory not writable" >&2
                return 1
            fi
            ;;
    esac
}

# Main script with error handling
main() {
    local input_file=$1
    
    # Validate input
    if ! validate_input "$input_file" "file"; then
        echo "Error: Invalid input file" >&2
        exit 1
    fi
    
    # Perform safe operations
    safe_file_operation "read" "$input_file"
    
    echo "Script completed successfully"
}

# Check if script is being sourced or executed
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

Integration with Other Tools

Shell scripts can integrate with databases, APIs, and other services.

Database Integration

#!/bin/bash

# MySQL database operations
mysql_query() {
    local host=$1
    local user=$2
    local password=$3
    local database=$4
    local query=$5
    
    mysql -h "$host" -u "$user" -p"$password" "$database" -e "$query"
}

# PostgreSQL operations
postgres_query() {
    local host=$1
    local user=$2
    local database=$3
    local query=$4
    
    PGPASSWORD=$5 psql -h "$host" -U "$user" -d "$database" -c "$query"
}

# SQLite operations
sqlite_query() {
    local database=$1
    local query=$2
    
    sqlite3 "$database" "$query"
}

# Example usage: Database backup
backup_database() {
    local db_type=$1
    local connection_params=$2
    local output_file=$3
    
    case $db_type in
        "mysql")
            mysql_dump "$connection_params" > "$output_file"
            ;;
        "postgresql")
            pg_dump "$connection_params" > "$output_file"
            ;;
        "sqlite")
            cp "$connection_params" "$output_file"
            ;;
    esac
    
    gzip "$output_file"
}

API Integration

#!/bin/bash

# REST API calls
api_call() {
    local method=$1
    local url=$2
    local data=$3
    local headers=$4
    
    local curl_cmd="curl -s -w '\n%{http_code}'"
    curl_cmd+=" -X $method"
    
    if [[ -n "$headers" ]]; then
        curl_cmd+=" $headers"
    fi
    
    if [[ -n "$data" ]]; then
        curl_cmd+=" -d '$data'"
    fi
    
    curl_cmd+=" '$url'"
    
    local result=$(eval $curl_cmd)
    local status_code=$(echo "$result" | tail -n1)
    local response=$(echo "$result" | head -n -1)
    
    if [[ $status_code -eq 200 ]]; then
        echo "$response"
    else
        echo "API Error: HTTP $status_code" >&2
        echo "$response" >&2
        return 1
    fi
}

# JSON processing with jq
parse_json_response() {
    local response=$1
    local field=$2
    
    echo "$response" | jq -r ".$field"
}

# Example: Weather API
get_weather() {
    local city=$1
    local api_key=$2
    
    local url="http://api.openweathermap.org/data/2.5/weather"
    local params="q=$city&appid=$api_key&units=metric"
    
    local response=$(curl -s "$url?$params")
    
    if echo "$response" | jq . >/dev/null 2>&1; then
        local temp=$(echo "$response" | jq -r '.main.temp')
        local description=$(echo "$response" | jq -r '.weather[0].description')
        
        echo "Weather in $city: $temp°C, $description"
    else
        echo "Error: Invalid response from weather API" >&2
        return 1
    fi
}

Performance Optimization

Optimize your scripts for better performance and resource usage.

Optimization Techniques

#!/bin/bash

# Use arrays instead of string operations
fast_processing() {
    local file=$1
    
    # Read file into array (faster for multiple operations)
    local lines=()
    mapfile -t lines < "$file"
    
    # Process with array operations
    for line in "${lines[@]}"; do
        # Process each line
        echo "$line" | tr '[:lower:]' '[:upper:]'
    done
}

# Use built-in commands instead of external processes
efficient_text_processing() {
    local input=$1
    
    # Use parameter expansion instead of external commands
    input=${input##*/}    # basename equivalent
    input=${input%.*}     # remove extension
    
    echo "$input"
}

# Parallel processing
parallel_processing() {
    local max_jobs=${1:-4}
    
    # Process files in parallel
    find . -type f -print0 | xargs -0 -P "$max_jobs" -I {} process_file {}
}

# Memory-efficient processing
streaming_processing() {
    local input_file=$1
    
    # Process line by line (memory efficient for large files)
    while IFS= read -r line; do
        # Process line
        echo "$line" | grep "pattern"
    done < "$input_file"
}

# Use bc for math operations instead of external commands
math_operations() {
    local num1=$1
    local num2=$2
    
    # Use built-in arithmetic for simple operations
    local sum=$((num1 + num2))
    local product=$((num1 * num2))
    
    # Use bc for complex operations
    local sqrt=$(echo "scale=4; sqrt($num1)" | bc)
    
    echo "Sum: $sum, Product: $product, Sqrt: $sqrt"
}

Testing and Debugging

Professional scripting includes comprehensive testing and debugging practices.

Debugging Tools

#!/bin/bash

# Enable debugging
set -x  # Print each command before execution

# Debug function
debug() {
    if [[ "${DEBUG:-0}" == "1" ]]; then
        echo "[DEBUG] $*" >&2
    fi
}

# Performance profiling
profile_script() {
    local start_time=$(date +%s.%N)
    
    # Run the main script logic
    "$@"
    
    local end_time=$(date +%s.%N)
    local runtime=$(echo "$end_time - $start_time" | bc)
    echo "Script execution time: ${runtime}s"
}

# Unit testing framework
assert_equals() {
    local expected=$1
    local actual=$2
    local message=$3
    
    if [[ "$expected" == "$actual" ]]; then
        echo "PASS: ${message:-'Test passed'}"
        return 0
    else
        echo "FAIL: ${message:-'Test failed'} - Expected: $expected, Got: $actual"
        return 1
    fi
}

# Test suite
run_tests() {
    local failed=0
    
    # Test function
    assert_equals "hello" "$(echo 'hello')" "Echo command"
    ((failed+=$?))
    
    assert_equals "2" "$((1+1))" "Math operation"
    ((failed+=$?))
    
    echo "Tests completed. Failures: $failed"
    return $failed
}

This advanced shell scripting guide equips you with powerful techniques for building sophisticated automation tools. Practice these concepts with real-world scenarios to become proficient in shell scripting.

For more Linux tutorials, explore our guide on command line basics and learn about window managers like i3 for a complete Linux experience.

The Advanced Bash-Scripting Guide is an excellent comprehensive resource, and the Bash Reference Manual provides detailed technical documentation for advanced users.

Last updated on