Advanced Control Flow ⭐⭐

Advanced Control Flow ⭐⭐

Previous: 03_Arrays_and_Data.md | Next: 05_Functions_and_Libraries.md


This lesson covers advanced control flow constructs in bash, including different test command variants, arithmetic evaluation, pattern matching, interactive menus, and sophisticated flow control patterns.

1. Test Commands Comparison

Bash provides multiple ways to test conditions. Understanding their differences is crucial for writing correct and efficient scripts.

The Three Test Constructs

#!/usr/bin/env bash

# 1. [ ] - POSIX test command (alias for 'test')
# 2. [[ ]] - Bash keyword (extended test)
# 3. (( )) - Arithmetic evaluation

# [ ] - Single bracket (POSIX)
if [ "$USER" = "root" ]; then
    echo "Running as root"
fi

# [[ ]] - Double bracket (Bash)
if [[ "$USER" == "root" ]]; then
    echo "Running as root"
fi

# (( )) - Arithmetic
if (( UID == 0 )); then
    echo "Running as root"
fi

Comprehensive Comparison Table

Feature [ ] (test) [[ ]] (keyword) (( )) (arithmetic)
POSIX Compliant Yes No (bash only) No (bash only)
Word Splitting Yes (dangerous) No (safe) N/A
Pathname Expansion Yes (dangerous) No (safe) N/A
Pattern Matching No Yes (==, != with globs) No
Regex Matching No Yes (=~) No
String Comparison =, != =, ==, !=, <, > No
Numeric Comparison -eq, -ne, -lt, -le, -gt, -ge Same ==, !=, <, <=, >, >=
Logical Operators -a, -o, ! &&, ||, ! &&, ||, !
Grouping \( \) (escaped) ( ) (normal) ( ) (normal)
Variable Quoting Required Optional N/A
Performance Slower (external command) Faster (builtin) Fastest

When to Use Each

#!/usr/bin/env bash

# Use [ ] for POSIX compatibility
if [ -f /etc/passwd ]; then
    echo "Password file exists"
fi

# Use [[ ]] for bash-specific features
# - Pattern matching
if [[ "$filename" == *.txt ]]; then
    echo "Text file"
fi

# - Regex matching
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "Valid email"
fi

# - Safe with unquoted variables
if [[ $var == "value" ]]; then  # Safe even if $var is empty
    echo "Match"
fi

# Use (( )) for arithmetic
if (( count > 100 )); then
    echo "Count exceeds 100"
fi

# - Complex arithmetic
if (( (x + y) * 2 > threshold )); then
    echo "Calculation exceeds threshold"
fi

Dangerous Patterns with [ ]

#!/usr/bin/env bash

# DANGEROUS: Word splitting
file="my document.txt"
[ -f $file ]  # Expands to: [ -f my document.txt ]
              # Error: too many arguments

# SAFE: Quote the variable
[ -f "$file" ]  # Correct

# DANGEROUS: Glob expansion
pattern="*.txt"
[ -f $pattern ]  # Expands to: [ -f file1.txt file2.txt ... ]
                 # Error: too many arguments

# SAFE: Quote or use [[
[ -f "$pattern" ]  # Checks for file literally named "*.txt"
[[ -f $pattern ]]  # No expansion, safe even unquoted

# DANGEROUS: Empty variables
var=""
[ $var = "value" ]  # Expands to: [ = "value" ]
                    # Error: unary operator expected

# SAFE: Quote the variable
[ "$var" = "value" ]  # Correct
[[ $var = "value" ]]  # Safe even unquoted

2. [[ ]] Advanced Features

Double brackets provide powerful features not available in single brackets.

Pattern Matching

#!/usr/bin/env bash

filename="document.txt"

# Glob pattern matching with ==
if [[ "$filename" == *.txt ]]; then
    echo "Text file"
fi

# Multiple patterns
if [[ "$filename" == *.txt || "$filename" == *.md ]]; then
    echo "Document file"
fi

# Negation
if [[ "$filename" != *.log ]]; then
    echo "Not a log file"
fi

# Case-insensitive (with nocaseglob)
shopt -s nocaseglob
if [[ "$filename" == *.TXT ]]; then
    echo "Text file (case-insensitive)"
fi
shopt -u nocaseglob

# Complex patterns (requires extglob)
shopt -s extglob
if [[ "$filename" == +(*.txt|*.md|*.rst) ]]; then
    echo "Documentation file"
fi

Regex Matching

#!/usr/bin/env bash

# =~ operator for regex matching

# Email validation
email="user@example.com"
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "Valid email"
fi

# Extract parts using BASH_REMATCH
if [[ "$email" =~ ^([^@]+)@(.+)$ ]]; then
    username="${BASH_REMATCH[1]}"
    domain="${BASH_REMATCH[2]}"
    echo "Username: $username"
    echo "Domain: $domain"
fi

# IP address validation
ip="192.168.1.1"
if [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
    echo "Valid IP format"
fi

# Phone number
phone="(555) 123-4567"
if [[ "$phone" =~ ^\([0-9]{3}\)\ [0-9]{3}-[0-9]{4}$ ]]; then
    echo "Valid phone number"
fi

# URL parsing
url="https://example.com:8080/path"
if [[ "$url" =~ ^(https?://)?([^:/]+)(:([0-9]+))?(/.*)?$ ]]; then
    protocol="${BASH_REMATCH[1]}"
    host="${BASH_REMATCH[2]}"
    port="${BASH_REMATCH[4]}"
    path="${BASH_REMATCH[5]}"

    echo "Protocol: ${protocol:-http://}"
    echo "Host: $host"
    echo "Port: ${port:-80}"
    echo "Path: ${path:-/}"
fi

String Comparison

#!/usr/bin/env bash

# [[ ]] supports < and > for lexicographic comparison

str1="apple"
str2="banana"

if [[ "$str1" < "$str2" ]]; then
    echo "$str1 comes before $str2"
fi

if [[ "$str1" > "$str2" ]]; then
    echo "$str1 comes after $str2"
fi

# Version comparison (simple)
ver1="1.2.3"
ver2="1.10.0"

# This is lexicographic, not numeric!
if [[ "$ver1" < "$ver2" ]]; then
    echo "$ver1 < $ver2"
else
    echo "$ver1 >= $ver2"
fi
# Output: 1.2.3 >= 1.10.0 (wrong! because "2" > "1" lexicographically)

Logical Operators

#!/usr/bin/env bash

# && (AND), || (OR), ! (NOT)

age=25
citizen=true

# AND
if [[ $age -ge 18 && "$citizen" == "true" ]]; then
    echo "Eligible to vote"
fi

# OR
if [[ $age -lt 13 || $age -gt 65 ]]; then
    echo "Discounted ticket"
fi

# NOT
if [[ ! -f /tmp/lock ]]; then
    echo "No lock file"
fi

# Grouping
if [[ ( $age -lt 18 || ! "$citizen" == "true" ) && -f /tmp/register ]]; then
    echo "Need to register"
fi

# Complex conditions
if [[ "$env" == "prod" && ( "$user" == "admin" || "$user" == "root" ) ]]; then
    echo "Production admin access"
fi

Preventing Word Splitting and Globbing

#!/usr/bin/env bash

# [[ ]] doesn't perform word splitting or glob expansion

files="*.txt"
sentence="hello world"

# With [ ], this would fail
# [ $sentence = "hello world" ]  # Error: too many arguments

# With [[ ]], it works fine
if [[ $sentence = "hello world" ]]; then
    echo "Match (unquoted variable works)"
fi

# No glob expansion
if [[ $files = "*.txt" ]]; then
    echo "Literal match (no expansion)"
fi

# Still, quoting is good practice for clarity
if [[ "$files" = "*.txt" ]]; then
    echo "Quoted match"
fi

3. Arithmetic Evaluation

Bash provides several ways to perform arithmetic operations.

Arithmetic Methods Comparison

#!/usr/bin/env bash

# Four methods for arithmetic

# 1. $(( )) - Arithmetic expansion (POSIX)
result=$((5 + 3))
echo "5 + 3 = $result"

# 2. let - Builtin command
let result=5+3
echo "5 + 3 = $result"

# 3. (( )) - Arithmetic evaluation (returns exit status)
(( result = 5 + 3 ))
echo "5 + 3 = $result"

# 4. expr - External command (deprecated, slow)
result=$(expr 5 + 3)
echo "5 + 3 = $result"

Comparison of Arithmetic Methods

Method POSIX Speed Use Case
$(( )) Yes Fast Calculations, assignments
let No Fast Multiple assignments
(( )) No Fast Conditionals, loops
expr Yes Slow Avoid (legacy)
bc External Slow Floating point

$(( )) Arithmetic Expansion

#!/usr/bin/env bash

# Basic operations
echo $((5 + 3))      # 8
echo $((10 - 4))     # 6
echo $((6 * 7))      # 42
echo $((20 / 3))     # 6 (integer division)
echo $((20 % 3))     # 2 (modulo)
echo $((2 ** 10))    # 1024 (exponentiation)

# Variables (no $ needed inside $(( )))
a=5
b=3
echo $((a + b))      # 8
echo $((a * b))      # 15

# Assignment
result=$((a * b + 2))
echo $result         # 17

# Increment/decrement
count=10
echo $((count++))    # 10 (post-increment)
echo $count          # 11

echo $((++count))    # 12 (pre-increment)
echo $count          # 12

echo $((count--))    # 12 (post-decrement)
echo $count          # 11

echo $((--count))    # 10 (pre-decrement)
echo $count          # 10

# Compound assignment
count=5
echo $((count += 3)) # 8
echo $((count *= 2)) # 16
echo $((count /= 4)) # 4
echo $((count %= 3)) # 1

# Bitwise operations
echo $((8 & 4))      # 0 (AND)
echo $((8 | 4))      # 12 (OR)
echo $((8 ^ 4))      # 12 (XOR)
echo $((~8))         # -9 (NOT)
echo $((8 << 2))     # 32 (left shift)
echo $((8 >> 2))     # 2 (right shift)

# Ternary operator
age=20
status=$((age >= 18 ? 1 : 0))
echo $status         # 1

let Command

#!/usr/bin/env bash

# Multiple assignments
let a=5 b=10 c=15

# Arithmetic
let "result = a + b * c"
echo $result  # 155

# No spaces around = when unquoted
let result=a+b
echo $result  # 15

# Increment
let count=10
let count++
echo $count  # 11

# Multiple operations
let "x = 5" "y = 10" "z = x + y"
echo $z  # 15

(( )) Arithmetic Evaluation

#!/usr/bin/env bash

# Used in conditionals (returns exit status)
count=5

if (( count > 0 )); then
    echo "Count is positive"
fi

if (( count >= 5 && count <= 10 )); then
    echo "Count in range"
fi

# Used in loops
for (( i=0; i<5; i++ )); do
    echo "Iteration $i"
done

# Assignment
(( result = 5 + 3 ))
echo $result  # 8

# Multiple operations
(( a = 5, b = 10, c = a + b ))
echo $c  # 15

# As standalone command
(( count++ ))
echo $count  # 6

Integer Limitations and Overflow

#!/usr/bin/env bash

# Bash uses signed long integers (typically 64-bit)

# Maximum value (2^63 - 1)
max=$((2**63 - 1))
echo "Max: $max"
# Max: 9223372036854775807

# Overflow wraps around
overflowed=$((max + 1))
echo "Overflowed: $overflowed"
# Overflowed: -9223372036854775808

# No floating point
result=$((10 / 3))
echo $result  # 3 (not 3.333...)

# Division by zero
# echo $((5 / 0))  # Error: division by 0

4. Floating Point with bc

For floating-point arithmetic, use the bc calculator.

Basic bc Usage

#!/usr/bin/env bash

# Simple calculation
result=$(echo "10 / 3" | bc -l)
echo $result  # 3.33333333333333333333

# Set precision (scale)
result=$(echo "scale=2; 10 / 3" | bc)
echo $result  # 3.33

# Multiple operations
result=$(echo "scale=4; (10 + 5) / 3" | bc)
echo $result  # 5.0000

# Using variables
a=10
b=3
result=$(echo "scale=2; $a / $b" | bc)
echo $result  # 3.33

Here-Document with bc

#!/usr/bin/env bash

# Multi-line bc script
result=$(bc -l <<EOF
    scale=2
    a = 10
    b = 3
    c = a / b
    c * 100
EOF
)
echo $result  # 333.33

# Complex calculation
calculate_compound_interest() {
    local principal=$1
    local rate=$2
    local time=$3
    local n=$4  # compounds per year

    bc -l <<EOF
        scale=2
        p = $principal
        r = $rate / 100
        t = $time
        n = $n
        a = p * (1 + r/n)^(n*t)
        print a
EOF
}

amount=$(calculate_compound_interest 1000 5 10 12)
echo "Amount: \$$amount"

Comparison Idiom

#!/usr/bin/env bash

# bc returns 1 for true, 0 for false

compare_float() {
    local a=$1
    local op=$2
    local b=$3

    result=$(echo "$a $op $b" | bc -l)
    [ "$result" -eq 1 ]
}

# Usage
if compare_float 3.14 ">" 3.0; then
    echo "3.14 > 3.0"
fi

if compare_float 2.5 "<=" 2.5; then
    echo "2.5 <= 2.5"
fi

# Inline
if (( $(echo "3.14 > 3.0" | bc -l) )); then
    echo "3.14 > 3.0"
fi

Practical Calculations

#!/usr/bin/env bash

# Percentage calculation
calculate_percentage() {
    local value=$1
    local total=$2
    echo "scale=2; ($value / $total) * 100" | bc
}

score=$(calculate_percentage 45 50)
echo "Score: ${score}%"

# Average
calculate_average() {
    local sum=0
    local count=$#

    for num in "$@"; do
        sum=$(echo "$sum + $num" | bc)
    done

    echo "scale=2; $sum / $count" | bc
}

avg=$(calculate_average 85.5 90.0 78.5 92.0)
echo "Average: $avg"

# Temperature conversion
celsius_to_fahrenheit() {
    local celsius=$1
    echo "scale=2; ($celsius * 9/5) + 32" | bc
}

fahrenheit_to_celsius() {
    local fahrenheit=$1
    echo "scale=2; ($fahrenheit - 32) * 5/9" | bc
}

temp_f=$(celsius_to_fahrenheit 25)
echo "25°C = ${temp_f}°F"

temp_c=$(fahrenheit_to_celsius 77)
echo "77°F = ${temp_c}°C"

# Unit conversion
miles_to_km() {
    echo "scale=2; $1 * 1.60934" | bc
}

km_to_miles() {
    echo "scale=2; $1 / 1.60934" | bc
}

echo "10 miles = $(miles_to_km 10) km"
echo "10 km = $(km_to_miles 10) miles"

5. Extended Globbing (extglob)

Extended globbing provides powerful pattern matching capabilities.

Enabling extglob

#!/usr/bin/env bash

# Enable extended globbing
shopt -s extglob

# Check if enabled
shopt -q extglob && echo "extglob is enabled"

Extended Glob Patterns

#!/usr/bin/env bash
shopt -s extglob

# ?(pattern) - Matches zero or one occurrence
# *(pattern) - Matches zero or more occurrences
# +(pattern) - Matches one or more occurrences
# @(pattern) - Matches exactly one occurrence
# !(pattern) - Matches anything except pattern

# Create test files
touch file.txt file.log file.bak test.txt data.csv

# ?(pattern) - zero or one
shopt -s nullglob
echo file.?(txt|log)
# file.txt file.log

# *(pattern) - zero or more
# Match files with any number of .bak extensions
touch file.bak file.bak.bak
echo *.*(bak)
# Lists all files

# +(pattern) - one or more
# Match files ending with one or more digits
touch file1.txt file22.txt file333.txt
echo file+([0-9]).txt
# file1.txt file22.txt file333.txt

# @(pattern) - exactly one
echo file.@(txt|log|csv)
# file.txt file.log

# !(pattern) - negation
echo !(*.txt)
# Lists all files except .txt files

# Clean up
rm -f file.* test.txt data.csv

File Matching Examples

#!/usr/bin/env bash
shopt -s extglob

# Remove all except specific types
# rm !(*.txt|*.log)  # Remove all except .txt and .log

# Match version numbers
# file-1.2.3.tar.gz
# file-+([0-9]).+([0-9]).+([0-9]).tar.gz

# Match optional prefix
# ?(pre-)test.txt matches both "test.txt" and "pre-test.txt"

# Match multiple extensions
# backup.@(tar.gz|zip|tar.bz2)

Using in case Statements

#!/usr/bin/env bash
shopt -s extglob

filename="$1"

case "$filename" in
    *.@(jpg|jpeg|png|gif))
        echo "Image file"
        ;;
    *.@(mp4|avi|mkv|mov))
        echo "Video file"
        ;;
    *.@(mp3|wav|flac|ogg))
        echo "Audio file"
        ;;
    *.@(txt|md|rst|doc))
        echo "Document file"
        ;;
    *.+(tar.@(gz|bz2|xz)|zip|rar))
        echo "Archive file"
        ;;
    !(*.))
        echo "No extension"
        ;;
    *)
        echo "Unknown file type"
        ;;
esac

6. select Menu

The select command creates interactive menus easily.

Basic select Menu

#!/usr/bin/env bash

# Simple menu
options=("Install" "Update" "Remove" "Quit")

select choice in "${options[@]}"; do
    case $choice in
        Install)
            echo "Installing..."
            break
            ;;
        Update)
            echo "Updating..."
            break
            ;;
        Remove)
            echo "Removing..."
            break
            ;;
        Quit)
            echo "Goodbye!"
            break
            ;;
        *)
            echo "Invalid option $REPLY"
            ;;
    esac
done

Custom PS3 Prompt

#!/usr/bin/env bash

# Customize the select prompt
PS3="Please select an option: "

options=("Start Server" "Stop Server" "Restart Server" "Exit")

select choice in "${options[@]}"; do
    case $choice in
        "Start Server")
            echo "Starting server..."
            ;;
        "Stop Server")
            echo "Stopping server..."
            ;;
        "Restart Server")
            echo "Restarting server..."
            ;;
        "Exit")
            echo "Exiting..."
            break
            ;;
        *)
            echo "Invalid option. Please try again."
            ;;
    esac
done

Input Validation

#!/usr/bin/env bash

PS3="Select environment: "

environments=("Development" "Staging" "Production")

select env in "${environments[@]}"; do
    # Check if choice is valid
    if [ -n "$env" ]; then
        echo "You selected: $env"

        # Confirm for production
        if [ "$env" = "Production" ]; then
            read -p "Are you sure you want to deploy to production? (yes/no): " confirm
            if [ "$confirm" = "yes" ]; then
                echo "Deploying to production..."
                break
            else
                echo "Deployment cancelled."
            fi
        else
            echo "Deploying to $env..."
            break
        fi
    else
        echo "Invalid selection. Please try again."
    fi
done

Nested Menus

#!/usr/bin/env bash

main_menu() {
    PS3="Main Menu: "
    options=("Database" "Web Server" "Cache" "Exit")

    select choice in "${options[@]}"; do
        case $choice in
            Database)
                database_menu
                ;;
            "Web Server")
                webserver_menu
                ;;
            Cache)
                cache_menu
                ;;
            Exit)
                echo "Goodbye!"
                return
                ;;
            *)
                echo "Invalid option"
                ;;
        esac
    done
}

database_menu() {
    PS3="Database Menu: "
    options=("Start" "Stop" "Backup" "Back")

    select choice in "${options[@]}"; do
        case $choice in
            Start)
                echo "Starting database..."
                ;;
            Stop)
                echo "Stopping database..."
                ;;
            Backup)
                echo "Backing up database..."
                ;;
            Back)
                return
                ;;
            *)
                echo "Invalid option"
                ;;
        esac
    done
}

webserver_menu() {
    PS3="Web Server Menu: "
    options=("Start" "Stop" "Reload" "Back")

    select choice in "${options[@]}"; do
        case $choice in
            Start|Stop|Reload)
                echo "${choice}ing web server..."
                ;;
            Back)
                return
                ;;
            *)
                echo "Invalid option"
                ;;
        esac
    done
}

cache_menu() {
    PS3="Cache Menu: "
    options=("Start" "Stop" "Clear" "Back")

    select choice in "${options[@]}"; do
        case $choice in
            Start|Stop)
                echo "${choice}ing cache..."
                ;;
            Clear)
                echo "Clearing cache..."
                ;;
            Back)
                return
                ;;
            *)
                echo "Invalid option"
                ;;
        esac
    done
}

# Start application
main_menu

Combining select with case

#!/usr/bin/env bash

# File operations menu
PS3="Select operation: "

files=(*.txt)
files+=("All files" "Quit")

select file in "${files[@]}"; do
    case $REPLY in
        $((${#files[@]}-1)))  # "All files" index
            echo "Processing all files..."
            for f in *.txt; do
                echo "  Processing $f"
            done
            ;;
        ${#files[@]})  # "Quit" index
            echo "Exiting..."
            break
            ;;
        *)
            if [ -n "$file" ] && [ "$file" != "All files" ] && [ "$file" != "Quit" ]; then
                echo "Processing: $file"
                # Process the file
            else
                echo "Invalid selection"
            fi
            ;;
    esac
done

7. Advanced case Patterns

The case statement supports sophisticated pattern matching.

Multiple Patterns

#!/usr/bin/env bash

response="$1"

case "$response" in
    y|Y|yes|Yes|YES)
        echo "Affirmative"
        ;;
    n|N|no|No|NO)
        echo "Negative"
        ;;
    q|Q|quit|Quit|QUIT|exit|Exit|EXIT)
        echo "Exiting..."
        exit 0
        ;;
    *)
        echo "Unknown response"
        ;;
esac

Character Ranges

#!/usr/bin/env bash

char="$1"

case "$char" in
    [a-z])
        echo "Lowercase letter"
        ;;
    [A-Z])
        echo "Uppercase letter"
        ;;
    [0-9])
        echo "Digit"
        ;;
    [[:space:]])
        echo "Whitespace"
        ;;
    [[:punct:]])
        echo "Punctuation"
        ;;
    *)
        echo "Other character"
        ;;
esac

Glob Patterns

#!/usr/bin/env bash

filename="$1"

case "$filename" in
    *.txt)
        echo "Text file"
        ;;
    *.log)
        echo "Log file"
        ;;
    *.tar.gz|*.tgz)
        echo "Gzipped tarball"
        ;;
    *.tar.bz2|*.tbz2)
        echo "Bzipped tarball"
        ;;
    backup_*)
        echo "Backup file"
        ;;
    *_[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9].*)
        echo "File with date stamp"
        ;;
    *)
        echo "Unknown file type"
        ;;
esac

Extended Glob in case (requires extglob)

#!/usr/bin/env bash
shopt -s extglob

file="$1"

case "$file" in
    *.@(jpg|jpeg|png|gif|bmp))
        echo "Image file: $file"
        file_type="image"
        ;;
    *.@(mp4|avi|mkv|mov|wmv))
        echo "Video file: $file"
        file_type="video"
        ;;
    *.@(mp3|wav|flac|ogg|m4a))
        echo "Audio file: $file"
        file_type="audio"
        ;;
    *.@(zip|tar.@(gz|bz2|xz)|rar|7z))
        echo "Archive file: $file"
        file_type="archive"
        ;;
    !(*.))
        echo "No extension: $file"
        file_type="no_extension"
        ;;
    *)
        echo "Unknown file type: $file"
        file_type="unknown"
        ;;
esac

echo "Type: $file_type"

Fall-Through (;& and ;;&)

#!/usr/bin/env bash

# ;& - Fall through to next pattern (bash 4.0+)
# ;;& - Continue testing patterns (bash 4.0+)

value="$1"

echo "Using ;& (fall through):"
case "$value" in
    [0-9])
        echo "It's a digit"
        ;&
    [a-z])
        echo "It's lowercase (if it was)"
        ;&
    [A-Z])
        echo "It's uppercase (if it was)"
        ;;
esac

echo -e "\nUsing ;;& (continue matching):"
case "$value" in
    [0-9]*)
        echo "Starts with digit"
        ;;&
    *[0-9])
        echo "Ends with digit"
        ;;&
    *[a-z]*)
        echo "Contains lowercase"
        ;;&
    *)
        echo "Matched catchall"
        ;;
esac

HTTP Status Code Example

#!/usr/bin/env bash

http_code="$1"

case "$http_code" in
    2[0-9][0-9])
        echo "Success"
        severity="info"
        ;;
    3[0-9][0-9])
        echo "Redirection"
        severity="info"
        ;;
    4[0-9][0-9])
        echo "Client Error"
        severity="warning"

        case "$http_code" in
            401)
                echo "  Unauthorized - check credentials"
                ;;
            403)
                echo "  Forbidden - insufficient permissions"
                ;;
            404)
                echo "  Not Found - check URL"
                ;;
        esac
        ;;
    5[0-9][0-9])
        echo "Server Error"
        severity="error"
        ;;
    *)
        echo "Unknown status code"
        severity="unknown"
        ;;
esac

echo "Severity: $severity"

8. Flow Control Patterns

Retry Loop with Backoff

#!/usr/bin/env bash

retry_with_backoff() {
    local max_attempts=$1
    shift
    local command=("$@")

    local attempt=1
    local delay=1

    while [ $attempt -le $max_attempts ]; do
        echo "Attempt $attempt of $max_attempts..."

        if "${command[@]}"; then
            echo "Success!"
            return 0
        fi

        if [ $attempt -lt $max_attempts ]; then
            echo "Failed. Retrying in ${delay}s..."
            sleep $delay
            delay=$((delay * 2))  # Exponential backoff
        fi

        ((attempt++))
    done

    echo "All attempts failed."
    return 1
}

# Usage
unreliable_command() {
    # Simulates command that fails randomly
    if (( RANDOM % 3 == 0 )); then
        return 0
    else
        return 1
    fi
}

retry_with_backoff 5 unreliable_command

State Machine

#!/usr/bin/env bash

# Simple state machine for order processing
process_order() {
    local state="pending"

    while true; do
        echo "Current state: $state"

        case "$state" in
            pending)
                echo "Validating order..."
                # Validation logic
                if validate_order; then
                    state="validated"
                else
                    state="rejected"
                fi
                ;;
            validated)
                echo "Processing payment..."
                if process_payment; then
                    state="paid"
                else
                    state="payment_failed"
                fi
                ;;
            paid)
                echo "Preparing shipment..."
                if prepare_shipment; then
                    state="shipped"
                else
                    state="shipment_failed"
                fi
                ;;
            shipped)
                echo "Order completed successfully!"
                state="completed"
                ;;
            completed|rejected|payment_failed|shipment_failed)
                echo "Terminal state: $state"
                break
                ;;
            *)
                echo "Unknown state: $state"
                break
                ;;
        esac

        sleep 1  # Simulate processing time
    done
}

validate_order() { return 0; }
process_payment() { return 0; }
prepare_shipment() { return 0; }

process_order

Dispatch Table

#!/usr/bin/env bash

# Dispatch table using associative array
declare -A commands

# Register commands
commands[start]="start_service"
commands[stop]="stop_service"
commands[restart]="restart_service"
commands[status]="check_status"
commands[reload]="reload_config"

# Command implementations
start_service() { echo "Starting service..."; }
stop_service() { echo "Stopping service..."; }
restart_service() { stop_service; start_service; }
check_status() { echo "Service is running"; }
reload_config() { echo "Reloading configuration..."; }

# Dispatcher
dispatch() {
    local command="$1"

    if [[ -v commands[$command] ]]; then
        ${commands[$command]}
    else
        echo "Unknown command: $command" >&2
        echo "Available commands: ${!commands[*]}" >&2
        return 1
    fi
}

# Usage
dispatch start
dispatch status
dispatch restart
dispatch invalid

Circuit Breaker Pattern

#!/usr/bin/env bash

# Circuit breaker for service calls
declare -A circuit_breaker=(
    [state]="closed"
    [failures]=0
    [threshold]=3
    [timeout]=10
)

call_service() {
    local service_name="$1"

    case "${circuit_breaker[state]}" in
        closed)
            if make_service_call "$service_name"; then
                circuit_breaker[failures]=0
                return 0
            else
                ((circuit_breaker[failures]++))

                if [ ${circuit_breaker[failures]} -ge ${circuit_breaker[threshold]} ]; then
                    circuit_breaker[state]="open"
                    circuit_breaker[opened_at]=$(date +%s)
                    echo "Circuit breaker OPEN" >&2
                fi
                return 1
            fi
            ;;
        open)
            local now=$(date +%s)
            local elapsed=$((now - circuit_breaker[opened_at]))

            if [ $elapsed -ge ${circuit_breaker[timeout]} ]; then
                circuit_breaker[state]="half_open"
                echo "Circuit breaker HALF-OPEN (testing)" >&2
                call_service "$service_name"
            else
                echo "Circuit breaker OPEN, failing fast" >&2
                return 1
            fi
            ;;
        half_open)
            if make_service_call "$service_name"; then
                circuit_breaker[state]="closed"
                circuit_breaker[failures]=0
                echo "Circuit breaker CLOSED (recovered)" >&2
                return 0
            else
                circuit_breaker[state]="open"
                circuit_breaker[opened_at]=$(date +%s)
                echo "Circuit breaker OPEN (still failing)" >&2
                return 1
            fi
            ;;
    esac
}

make_service_call() {
    local service="$1"
    # Simulate service call
    echo "Calling $service..."
    (( RANDOM % 2 == 0 ))  # 50% success rate
}

# Test circuit breaker
for i in {1..10}; do
    echo "--- Attempt $i ---"
    if call_service "external-api"; then
        echo "Success"
    else
        echo "Failed"
    fi
    sleep 1
done

Practice Problems

Problem 1: Advanced Input Validator

Create a comprehensive input validation library that: - Validates different data types (email, URL, IP, phone, credit card, date, etc.) - Supports custom regex patterns - Provides detailed error messages - Returns structured validation results (pass/fail + error details) - Supports composite validation (multiple validators on one input) - Performance-optimized (uses appropriate test constructs)

Example:

validate "user@example.com" email
validate "192.168.1.1" ipv4
validate "2024-02-13" date --format "YYYY-MM-DD"
validate "password123" password --min-length 8 --require-digit --require-special

Problem 2: Expression Evaluator

Build an expression evaluator that: - Parses and evaluates arithmetic expressions - Supports variables and functions - Handles floating-point math (via bc integration) - Supports logical expressions - Provides error reporting with position - Includes common math functions (sin, cos, sqrt, etc.)

Example:

eval_expr "2 + 3 * 4"                    # 14
eval_expr "x = 5; y = 10; x + y"         # 15
eval_expr "sqrt(16) + pow(2, 3)"         # 12
eval_expr "if(x > 5, x * 2, x / 2)" x=10 # 20

Problem 3: Pattern Matcher

Develop a pattern matching tool that: - Matches files against multiple pattern types (glob, regex, extended glob) - Supports inclusion and exclusion patterns - Provides pattern explanations - Optimizes pattern matching order - Generates pattern from examples - Validates patterns before use

Example:

pattern_match "file.txt" --glob "*.txt" --exclude "temp_*"
pattern_match "192.168.1.1" --regex "^([0-9]{1,3}\.){3}[0-9]{1,3}$"
pattern_explain "*.@(jpg|png|gif)"
pattern_generate --from-examples "test1.log" "test2.log" "prod.log"

Problem 4: Interactive Wizard

Create an interactive wizard framework that: - Builds multi-step forms - Validates input at each step - Supports conditional steps (skip based on previous answers) - Provides summary and confirmation - Allows going back to previous steps - Saves progress (resume later) - Supports different input types (text, select, multi-select, yes/no)

Example:

wizard_start "Setup Database"
wizard_step "host" --prompt "Database host" --default "localhost" --validate ipv4_or_hostname
wizard_step "port" --prompt "Port" --default 5432 --validate port_number
wizard_step "ssl" --prompt "Use SSL?" --type yesno
wizard_step "cert" --prompt "Certificate path" --if "ssl==yes" --validate file_exists
wizard_confirm
wizard_execute

Problem 5: Smart Retry System

Implement an advanced retry system with: - Multiple retry strategies (constant, linear, exponential backoff, fibonacci) - Jitter (randomness to prevent thundering herd) - Circuit breaker integration - Success/failure callbacks - Timeout per attempt - Conditional retry (retry only on specific errors) - Metrics collection (attempts, success rate, average latency)

Example:

retry --strategy exponential --max-attempts 5 --initial-delay 1 --max-delay 30 \
      --jitter 0.1 --timeout 10 --on-success log_success --on-failure alert \
      --retry-on "1,2,3" -- curl https://api.example.com/endpoint

Previous: 03_Arrays_and_Data.md | Next: 05_Functions_and_Libraries.md

to navigate between lessons