Course Outline (Part 13)

Welcome to Part 13 of the Linux Bash Course. In this section, we transition from theory to practice. You will build three robust, real-world utility projects that apply everything you have learned about loops, logic, filters, and system files.


Chapter 37: Project 1 – Interactive Menu Script

37.1 Project Goal and Requirements: User Interface Design in Terminal

The goal of this project is to create an interactive terminal dashboard. The script must display a clean visual text interface, accept user input to run system diagnostic commands, and loop indefinitely until the user selects “Exit”.

37.2 Defining Project Structure, Variables, and Help Menu

Start by setting up the file layout. We use a standard shebang, strict shell flags (-u to catch unset variables), and define clean color variables using ANSI escape codes for styling.

#!/bin/bash
set -u

# Define color constants
BLUE='\033[0;34m'
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color

37.3 Prompting for User Input: Using read with timeouts

We use the read command to capture input. The -p option prints a custom prompt, and -r prevents backslash escapes from interpolating.

  • Syntax:

    read [options] variable_name
  • Example Command:

    read -r -p "Enter your choice [1-4]: " choice
  • Expected Output:

    Enter your choice [1-4]: _
  • Flag & Command Breakdown:

    • read: Reads input from standard input.
    • -r: Disables backslash escape sequences (raw input mode).
    • -p: Displays a prompt string before waiting for input.
    • choice: Variable where the user input is saved.

37.4 Implementing a robust case statement loop for menu options

To process choices, wrap a conditional case statement inside an infinite while true loop.

while true; do
    # Display menu options
    echo "1. Show System Details"
    echo "2. List Logged-In Users"
    echo "3. Exit"
    
    read -r -p "Choice: " opt
    case "$opt" in
        1) show_system_info ;;
        2) list_users ;;
        3) break ;;
        *) echo "Invalid option!" ;;
    esac
done

37.5 Menu Option 1: Displaying System Details and Host Information

To display host details, we call the hostnamectl command and extract specific fields.

  • Syntax:

    hostnamectl [options]
  • Example Command:

    hostnamectl status --static
  • Expected Output:

    ubuntu-server-22
  • Flag & Command Breakdown:

    • hostnamectl: Configures and queries the system hostname.
    • status: Displays current hostname status information.
    • --static: Outputs only the static system hostname.

37.6 Menu Option 2: Checking and listing logged-in users

We query the system database using who to show who is currently logged into the shell.

  • Syntax:

    who [options]
  • Example Command:

    who -H
  • Expected Output:

    NAME     LINE         TIME             COMMENT
    suresh   tty1         2026-06-12 06:00
  • Flag & Command Breakdown:

    • who: Displays active user sessions.
    • -H: Prints table column headings at the top of the output.

37.7 Menu Option 3: Showing live memory and CPU percentages

We read system memory details using free.

  • Syntax:

    free [options]
  • Example Command:

    free -h -t
  • Expected Output:

                   total        used        free      shared  buff/cache   available
    Mem:           7.7Gi       2.1Gi       3.2Gi       215Mi       2.4Gi       5.2Gi
    Swap:          2.0Gi       100Mi       1.9Gi
    Total:         9.7Gi       2.2Gi       5.1Gi
  • Flag & Command Breakdown:

    • free: Displays RAM and swap usage.
    • -h: Human-readable format.
    • -t: Displays a summary line containing totals.

37.8 Validating input arguments and handling empty or incorrect options

Always validate variables to prevent errors. Ensure input is not empty using a conditional expression:

if [[ -z "$choice" ]]; then
    echo "Input cannot be empty. Please enter a valid number."
    continue
fi

If the user inputs letters when numbers are expected, a fallback case * catches it and prompts the user to try again.

37.9 Adding colors and clear/reset layouts for terminal feedback

Use the clear utility to reset the terminal layout, creating a dashboard feel.

  • Syntax:

    clear
  • Example Command:

    clear
  • Expected Output: (The terminal screen is cleared, and the cursor returns to the top-left)

  • Flag & Command Breakdown:

    • clear: Clears the screen.

37.10 Full Code Assembly and Shell testing of the Menu Script

Create the file dashboard.sh, make it executable, and run it:

# Save to file, make executable, and run:
chmod +x dashboard.sh
./dashboard.sh

Here is the complete script:

#!/bin/bash
set -u

# Colors
BLUE='\033[0;34m'
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'

while true; do
    echo -e "${BLUE}=== SYSTEM DASHBOARD ===${NC}"
    echo "1. Hostname Info"
    echo "2. Logged-in Users"
    echo "3. Free Memory"
    echo "4. Exit"
    read -r -p "Select [1-4]: " opt
    case "$opt" in
        1) hostnamectl ;;
        2) who -H ;;
        3) free -h ;;
        4) echo "Goodbye!"; exit 0 ;;
        *) echo -e "${RED}Invalid Selection${NC}" ;;
    esac
done

Chapter 38: Project 2 – System Health Monitor

38.1 Project Architecture: Monitoring system metrics via background script

This daemon runs in the background. It periodically checks resource usage and alerts the administrator if any metric exceeds pre-configured thresholds.

38.2 Setting Up threshold variables for CPU, Memory, and Disk Alerting

Establish baseline limits at the beginning of the script:

# Threshold limits (percent)
CPU_LIMIT=80
MEM_LIMIT=85
DISK_LIMIT=90

ALERT_LOG="/var/log/system_health.log"

38.3 Getting CPU usage using top and awk commands

Extract CPU idle time using top and calculate the used percentage.

  • Syntax:

    top -b -n 1
  • Example Command:

    top -b -n 1 | grep "Cpu(s)" | awk '{print $8}'
  • Expected Output:

    92.5
  • Flag & Command Breakdown:

    • top: Interactive system monitor.
    • -b: Batch mode (runs once and exits, allowing command output redirection).
    • -n 1: Runs for a single loop iteration.
    • grep "Cpu(s)": Extracts the line containing CPU statistics.
    • awk '{print $8}': Isolates the 8th field (percentage of idle CPU).

38.4 Extracting RAM usage percentages using free and awk

Filter output to calculate the current memory usage percentage.

  • Syntax:

    free | awk 'NR==2{printf "%.0f", $3/$2*100}'
  • Example Command:

    free | awk 'NR==2{printf "%.0f", $3/$2*100}'
  • Expected Output:

    27
  • Flag & Command Breakdown:

    • free: Displays memory layout.
    • awk: Processes line 2 (NR==2) and divides Used memory ($3) by Total memory ($2), multiplying by 100 to get a percentage.

38.5 Querying disk usage metrics using df parsing pipelines

Check disk usage on the root partition.

  • Syntax:

    df [path] | awk 'NR==2 {print $5}'
  • Example Command:

    df / | awk 'NR==2 {print $5}' | sed 's/%//'
  • Expected Output:

    45
  • Flag & Command Breakdown:

    • df /: Checks the root disk mount.
    • awk 'NR==2 {print $5}': Extracts the percentage column.
    • sed 's/%//': Strips the % sign, leaving only the integer value for comparison.

38.6 Conditional branching: Evaluating values against thresholds

Compare the extracted system values against your defined thresholds:

if [ "$CPU_USAGE" -gt "$CPU_LIMIT" ]; then
    trigger_alert "CPU usage high: $CPU_USAGE%"
fi

38.7 Creating alerts: Writing incidents to log files and syslogs

We use logger to write warning logs to the local system log manager.

  • Syntax:

    logger [options] message
  • Example Command:

    logger -p user.warning "ALERT: CPU usage exceeded threshold"
  • Expected Output: (No output in the console. Writes the warning message to /var/log/syslog)

  • Flag & Command Breakdown:

    • logger: Writes entries to system logs.
    • -p user.warning: Sets the log priority level to “warning”.

38.8 Integrating email or Discord/Slack Webhook notifications

You can send alert logs to external messaging APIs using curl:

curl -H "Content-Type: application/json" \
     -X POST \
     -d "{\"content\": \"$ALERT_MESSAGE\"}" \
     $DISCORD_WEBHOOK_URL

38.9 Designing the script to run continuously as a daemon process

To run the script as a daemon, wrap the logic in an infinite loop that runs every few seconds using sleep:

while true; do
    check_system_health
    sleep 60
done

38.10 Script validation, unit testing, and full code assembly

Assemble the full system health monitor script:

#!/bin/bash
set -u

CPU_LIMIT=80
MEM_LIMIT=85
DISK_LIMIT=90

# Calculate usage
CPU_IDLE=$(top -b -n 1 | grep "Cpu(s)" | awk '{print $8}')
CPU_USED=$(echo "100 - $CPU_IDLE" | bc | cut -d. -f1)
MEM_USED=$(free | awk 'NR==2{printf "%.0f", $3/$2*100}')
DISK_USED=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')

if [ "$CPU_USED" -gt "$CPU_LIMIT" ]; then
    logger -p user.warning "CRITICAL: CPU usage is at $CPU_USED%"
fi

if [ "$MEM_USED" -gt "$MEM_LIMIT" ]; then
    logger -p user.warning "CRITICAL: Memory usage is at $MEM_USED%"
fi

if [ "$DISK_USED" -gt "$DISK_LIMIT" ]; then
    logger -p user.warning "CRITICAL: Disk space usage is at $DISK_USED%"
fi

Chapter 39: Project 3 – File Organizer

39.1 Project Outline: Auto-sorting downloads and messy directories

This script parses a target folder (like a Downloads folder) and moves files into organized subfolders (e.g., Images, Documents, Archives) based on their file extensions.

39.2 Declaring Target Directories for Images, Documents, and Archives

Define target folder variables at the beginning of the script:

TARGET_DIR="/home/suresh/Downloads"

IMG_DIR="$TARGET_DIR/Images"
DOC_DIR="$TARGET_DIR/Documents"
ARC_DIR="$TARGET_DIR/Archives"

39.3 Checking and creating destination folders if they do not exist

Ensure target directories exist before moving files:

for folder in "$IMG_DIR" "$DOC_DIR" "$ARC_DIR"; do
    if [ ! -d "$folder" ]; then
        mkdir -p "$folder"
    fi
done

39.4 Parsing files in a directory using Bash wildcards and loops

Iterate through the target files, ignoring directories:

for file in "$TARGET_DIR"/*; do
    if [ -f "$file" ]; then
        # Process the file
    fi
done

39.5 Determining file extensions and file types using the file utility

The file utility identifies a file’s MIME type or format based on its contents rather than its extension.

  • Syntax:

    file [options] filename
  • Example Command:

    file --mime-type -b logo.svg
  • Expected Output:

    image/svg+xml
  • Flag & Command Breakdown:

    • file: Determines file type.
    • --mime-type: Returns only the standard MIME format.
    • -b: Brief mode; omits the filename from the output.

39.6 Implementing the sorting logic with mv and error handling

Move files into their respective folders based on their extensions:

ext="${file##*.}"
case "${ext,,}" in
    jpg|png|gif|svg)
        mv "$file" "$IMG_DIR/" ;;
    pdf|docx|txt|xlsx)
        mv "$file" "$DOC_DIR/" ;;
    zip|tar|gz|xz)
        mv "$file" "$ARC_DIR/" ;;
esac

39.7 Adding dry-run capability to preview changes before moving files

Use a command line flag to check what changes the script would make without moving files:

DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
    DRY_RUN=true
fi

If $DRY_RUN is true, print the move commands instead of executing them.

39.8 Adding logs: Tracking files moved and generating summaries

Log operations to standard output or a file:

echo "[$(date)] Sorted file: $(basename "$file") -> $target_folder" >> "$TARGET_DIR/organizer.log"

39.9 Automating the script using cron scheduling or directory watching

To run the organizer automatically every hour, add it to your crontab:

0 * * * * /home/suresh/bin/organizer.sh --silent

39.10 Full Code Assembly and Script validation of the File Organizer

Save this script as organizer.sh:

#!/bin/bash
set -eu

TARGET_DIR="/home/suresh/Downloads"
DRY_RUN=false

# Check for dry-run option
if [ "${1:-}" = "--dry-run" ]; then
    DRY_RUN=true
    echo "=== DRY RUN MODE ==="
fi

# Create directories
mkdir -p "$TARGET_DIR/Images" "$TARGET_DIR/Documents" "$TARGET_DIR/Archives"

for file in "$TARGET_DIR"/*; do
    # Skip directories
    [ -d "$file" ] && continue
    [ -f "$file" ] || continue
    
    filename=$(basename "$file")
    ext="${filename##*.}"
    
    target_folder=""
    case "${ext,,}" in
        jpg|jpeg|png|gif|svg) target_folder="$TARGET_DIR/Images" ;;
        pdf|doc|docx|txt|xlsx) target_folder="$TARGET_DIR/Documents" ;;
        zip|tar|gz|tgz|xz) target_folder="$TARGET_DIR/Archives" ;;
        *) continue ;; # Skip other file extensions
    esac
    
    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would move: $filename -> $target_folder"
    else
        mv "$file" "$target_folder/"
        echo "Moved: $filename -> $target_folder"
    fi
done

External Resources