Shell Scripting

Shell Scripting Projects

35 hands-on projects progressing from basic shell scripts to expert-level automation systems for Raspberry Pi and other SBCs. Each project builds on previous skills.


Beginner Projects (1–8)

Project 1 — Hello, System!

Print a personalised system greeting with key info.

bash
#!/bin/bash
echo "==============================="
echo "  Welcome, $USER!"
echo "==============================="
echo "Hostname : $(hostname)"
echo "Date/Time: $(date '+%Y-%m-%d %H:%M:%S')"
echo "OS       : $(lsb_release -ds 2>/dev/null || uname -o)"
echo "Kernel   : $(uname -r)"
echo "Uptime   : $(uptime -p)"
echo "IP       : $(hostname -I | awk '{print $1}')"
echo "==============================="

Project 2 — Directory Organiser

Sort files in a folder into subfolders by extension.

bash
#!/bin/bash
TARGET="${1:-.}"
cd "$TARGET" || exit 1

for FILE in *; do
    [ -f "$FILE" ] || continue
    EXT="${FILE##*.}"
    [ "$EXT" = "$FILE" ] && EXT="no_extension"
    mkdir -p "$EXT"
    mv "$FILE" "$EXT/"
    echo "Moved $FILE → $EXT/"
done
echo "Done organising $TARGET"

Usage: ./organise.sh ~/Downloads


Project 3 — Timestamped Backup

Backup a directory with a date-stamped archive.

bash
#!/bin/bash
SOURCE="${1:?Usage: $0 <source_dir> [dest_dir]}"
DEST="${2:-$HOME/backups}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
ARCHIVE="$DEST/backup_$(basename "$SOURCE")_$TIMESTAMP.tar.gz"

mkdir -p "$DEST"
tar -czf "$ARCHIVE" "$SOURCE"
SIZE=$(du -sh "$ARCHIVE" | cut -f1)
echo "Backup complete: $ARCHIVE ($SIZE)"

Usage: ./backup.sh /home/pi/projects


Project 4 — Simple Calculator

bash
#!/bin/bash
echo -n "Enter first number : "; read A
echo -n "Enter operator (+,-,*,/) : "; read OP
echo -n "Enter second number: "; read B

case "$OP" in
    +) echo "Result: $(echo "$A + $B" | bc)" ;;
    -) echo "Result: $(echo "$A - $B" | bc)" ;;
    \*) echo "Result: $(echo "$A * $B" | bc)" ;;
    /) echo "Result: $(echo "scale=4; $A / $B" | bc)" ;;
    *) echo "Unknown operator: $OP"; exit 1 ;;
esac

Project 5 — Command Checker

Check whether required tools are installed before a project setup.

bash
#!/bin/bash
REQUIRED=(git python3 cmake make gcc openocd)
MISSING=()

for CMD in "${REQUIRED[@]}"; do
    if command -v "$CMD" &>/dev/null; then
        echo "[OK]     $CMD ($(command -v "$CMD"))"
    else
        echo "[MISSING] $CMD"
        MISSING+=("$CMD")
    fi
done

if [ ${#MISSING[@]} -gt 0 ]; then
    echo ""
    echo "Install missing tools with:"
    echo "  sudo apt install -y ${MISSING[*]}"
    exit 1
fi
echo "All tools available."

Project 6 — Countdown Timer

bash
#!/bin/bash
SECONDS_LEFT="${1:?Usage: $0 <seconds>}"

while [ "$SECONDS_LEFT" -gt 0 ]; do
    printf "\r  Remaining: %02d:%02d  " \
        $((SECONDS_LEFT / 60)) $((SECONDS_LEFT % 60))
    sleep 1
    ((SECONDS_LEFT--))
done
echo ""
echo "Time's up!"
# Play a beep if speakers are available
command -v aplay &>/dev/null && aplay /usr/share/sounds/alsa/Front_Left.wav 2>/dev/null

Project 7 — Large File Finder

Find and report files larger than a given size.

bash
#!/bin/bash
DIR="${1:-$HOME}"
MIN_MB="${2:-100}"

echo "Searching $DIR for files larger than ${MIN_MB}MB..."
echo ""
find "$DIR" -type f -size +"${MIN_MB}M" -printf '%s %p\n' 2>/dev/null \
    | sort -rn \
    | awk '{printf "%-10s %s\n", int($1/1024/1024)"MB", $2}' \
    | head -30

Project 8 — User Account Reporter

bash
#!/bin/bash
echo "=== System User Report ==="
echo ""
echo "--- Login Users ---"
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, "| Home:", $6, "| Shell:", $7}' /etc/passwd

echo ""
echo "--- Currently Logged In ---"
who

echo ""
echo "--- Last 10 Logins ---"
last | head -10

Intermediate Projects (9–18)

Project 9 — CPU Temperature Monitor (Raspberry Pi)

bash
#!/bin/bash
LOG="${1:-/var/log/cpu_temp.log}"
INTERVAL="${2:-5}"
WARN_TEMP=70
CRIT_TEMP=80

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

while true; do
    RAW=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null)
    if [ -z "$RAW" ]; then
        log "ERROR: Cannot read CPU temperature."
        sleep "$INTERVAL"; continue
    fi

    TEMP=$((RAW / 1000))
    FRAC=$(( (RAW % 1000) / 100 ))

    if   [ "$TEMP" -ge "$CRIT_TEMP" ]; then STATUS="CRITICAL"
    elif [ "$TEMP" -ge "$WARN_TEMP" ]; then STATUS="WARNING"
    else STATUS="OK"
    fi

    log "CPU Temp: ${TEMP}.${FRAC}°C [$STATUS]"
    sleep "$INTERVAL"
done

Usage: ./cpu_temp_monitor.sh /var/log/cpu_temp.log 10


Project 10 — Disk Space Alert

Send an alert when disk usage exceeds a threshold.

bash
#!/bin/bash
THRESHOLD="${1:-80}"
LOG="/var/log/disk_alert.log"

check_disk() {
    df -h | awk 'NR>1' | while read -r FS SIZE USED AVAIL USE MP; do
        PCT="${USE%%%}"
        if [ "$PCT" -ge "$THRESHOLD" ]; then
            MSG="[ALERT] $MP is ${PCT}% full (${USED}/${SIZE})"
            echo "[$(date '+%Y-%m-%d %H:%M:%S')] $MSG" | tee -a "$LOG"
            # Uncomment to send email:
            # echo "$MSG" | mail -s "Disk Alert on $(hostname)" admin@example.com
        fi
    done
}

while true; do
    check_disk
    sleep 300
done

Project 11 — System Health Report

Generate a full HTML system report.

bash
#!/bin/bash
REPORT="/tmp/system_report_$(date +%Y%m%d_%H%M%S).html"

cat << EOF > "$REPORT"
<!DOCTYPE html><html><head>
<title>System Report — $(hostname)</title>
<style>body{font-family:monospace;background:#111;color:#0f0;padding:20px}
h2{color:#0af}pre{background:#222;padding:10px}</style>
</head><body>
<h1>System Report — $(hostname)</h1>
<p>Generated: $(date)</p>
<h2>CPU</h2><pre>$(lscpu | grep -E 'Model|Core|Thread|MHz')</pre>
<h2>Memory</h2><pre>$(free -h)</pre>
<h2>Disk</h2><pre>$(df -h)</pre>
<h2>Network</h2><pre>$(ip addr show | grep -E 'inet |link')</pre>
<h2>Top Processes</h2><pre>$(ps aux --sort=-%cpu | head -10)</pre>
<h2>Recent Logs</h2><pre>$(journalctl -n 20 --no-pager 2>/dev/null)</pre>
</body></html>
EOF

echo "Report saved: $REPORT"
command -v xdg-open &>/dev/null && xdg-open "$REPORT"

Project 12 — Auto Update Script

Keep a Raspberry Pi automatically updated and log changes.

bash
#!/bin/bash
set -e
LOG="/var/log/auto_update.log"
exec >> "$LOG" 2>&1

echo "=== Update started: $(date) ==="
apt-get update -qq
UPGRADABLE=$(apt-get -s upgrade | grep "^Inst" | wc -l)
echo "Packages to upgrade: $UPGRADABLE"

if [ "$UPGRADABLE" -gt 0 ]; then
    apt-get upgrade -y
    echo "Upgrade complete."
    if [ -f /var/run/reboot-required ]; then
        echo "Reboot required — scheduling..."
        shutdown -r +1 "Auto-update reboot"
    fi
else
    echo "System is up to date."
fi
apt-get autoremove -y
apt-get autoclean
echo "=== Update finished: $(date) ==="

Install as cron: 0 3 * * 0 sudo /home/pi/scripts/auto_update.sh


Project 13 — Log Rotator

Rotate log files older than N days.

bash
#!/bin/bash
LOG_DIR="${1:?Usage: $0 <log_dir> [max_days]}"
MAX_DAYS="${2:-7}"
ARCHIVE_DIR="$LOG_DIR/archive"

mkdir -p "$ARCHIVE_DIR"

find "$LOG_DIR" -maxdepth 1 -name "*.log" -mtime "+$MAX_DAYS" | while read -r FILE; do
    BASENAME=$(basename "$FILE")
    DEST="$ARCHIVE_DIR/${BASENAME%.log}_$(date +%Y%m%d).log.gz"
    gzip -c "$FILE" > "$DEST"
    rm "$FILE"
    echo "Rotated: $BASENAME → $(basename "$DEST")"
done

find "$ARCHIVE_DIR" -name "*.gz" -mtime +30 -delete
echo "Log rotation complete for $LOG_DIR"

Project 14 — Network Connectivity Monitor

Monitor ping to multiple hosts and log outages.

bash
#!/bin/bash
HOSTS=("8.8.8.8" "1.1.1.1" "192.168.1.1")
LOG="/var/log/network_monitor.log"
INTERVAL=30

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

declare -A LAST_STATE
for H in "${HOSTS[@]}"; do LAST_STATE[$H]="up"; done

while true; do
    for HOST in "${HOSTS[@]}"; do
        if ping -c 2 -W 2 "$HOST" &>/dev/null; then
            if [ "${LAST_STATE[$HOST]}" = "down" ]; then
                log "RECOVERED: $HOST is reachable again."
                LAST_STATE[$HOST]="up"
            fi
        else
            if [ "${LAST_STATE[$HOST]}" = "up" ]; then
                log "ALERT: $HOST is unreachable!"
                LAST_STATE[$HOST]="down"
            fi
        fi
    done
    sleep "$INTERVAL"
done

Project 15 — Batch File Renamer

Rename files in bulk with a pattern.

bash
#!/bin/bash
# Usage: ./rename.sh <dir> <find_pattern> <replace_pattern>
DIR="${1:?Usage: $0 <dir> <find> <replace>}"
FIND="${2:?}"
REPLACE="${3:?}"
COUNT=0

find "$DIR" -maxdepth 1 -type f | while read -r FILE; do
    BASE=$(basename "$FILE")
    NEW="${BASE//$FIND/$REPLACE}"
    if [ "$BASE" != "$NEW" ]; then
        mv "$FILE" "$(dirname "$FILE")/$NEW"
        echo "Renamed: $BASE → $NEW"
        ((COUNT++)) || true
    fi
done
echo "Done."

Usage: ./rename.sh ./captures IMG Photo


Project 16 — Interactive Menu System

A reusable menu framework for SBC management scripts.

bash
#!/bin/bash
TITLE="Raspberry Pi Manager"

show_menu() {
    clear
    echo "╔════════════════════════════╗"
    printf  "║  %-26s ║\n" "$TITLE"
    echo "╠════════════════════════════╣"
    echo "║  1) System Info            ║"
    echo "║  2) CPU Temperature        ║"
    echo "║  3) Disk Usage             ║"
    echo "║  4) Network Info           ║"
    echo "║  5) List Services          ║"
    echo "║  6) Reboot                 ║"
    echo "║  q) Quit                   ║"
    echo "╚════════════════════════════╝"
    echo -n "  Choice: "
}

while true; do
    show_menu
    read -r CHOICE
    case "$CHOICE" in
        1) uname -a; free -h; df -h ;;
        2) cat /sys/class/thermal/thermal_zone0/temp | awk '{printf "%.1f°C\n", $1/1000}' ;;
        3) df -h ;;
        4) ip addr; ip route ;;
        5) systemctl list-units --type=service --state=running ;;
        6) read -p "Reboot now? [y/N] " C; [[ "$C" =~ ^[Yy] ]] && sudo reboot ;;
        q|Q) echo "Bye!"; exit 0 ;;
        *) echo "Invalid choice." ;;
    esac
    echo; read -p "Press Enter to continue..."
done

Project 17 — GPIO LED Blinker (Raspberry Pi)

Blink an LED using shell — no Python needed.

bash
#!/bin/bash
# Requires: GPIO pin wired to LED
# On Raspberry Pi: GPIO 17 = physical pin 11
GPIO_PIN="${1:-17}"
BLINK_COUNT="${2:-10}"
DELAY="${3:-0.5}"

GPIO_PATH="/sys/class/gpio"

cleanup() {
    echo "$GPIO_PIN" > "$GPIO_PATH/unexport" 2>/dev/null
    echo "GPIO $GPIO_PIN released."
}
trap cleanup EXIT

# Export pin
echo "$GPIO_PIN" > "$GPIO_PATH/export"
sleep 0.1
echo "out" > "$GPIO_PATH/gpio${GPIO_PIN}/direction"

echo "Blinking GPIO $GPIO_PIN — $BLINK_COUNT times..."
for ((i=0; i<BLINK_COUNT; i++)); do
    echo 1 > "$GPIO_PATH/gpio${GPIO_PIN}/value"
    sleep "$DELAY"
    echo 0 > "$GPIO_PATH/gpio${GPIO_PIN}/value"
    sleep "$DELAY"
    echo "Blink $((i+1))/$BLINK_COUNT"
done

Project 18 — Serial Port Logger

Log incoming serial data from ESP32 or sensor module.

bash
#!/bin/bash
PORT="${1:-/dev/ttyUSB0}"
BAUD="${2:-115200}"
LOG="serial_$(date +%Y%m%d_%H%M%S).log"

if [ ! -c "$PORT" ]; then
    echo "ERROR: $PORT not found."
    echo "Available ports: $(ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null)"
    exit 1
fi

stty -F "$PORT" "$BAUD" cs8 -cstopb -parenb raw

echo "Logging $PORT at $BAUD baud → $LOG"
echo "Press Ctrl+C to stop."

while IFS= read -r LINE; do
    ENTRY="[$(date '+%Y-%m-%d %H:%M:%S')] $LINE"
    echo "$ENTRY"
    echo "$ENTRY" >> "$LOG"
done < "$PORT"

Advanced Projects (19–26)

Project 19 — Wi-Fi Reconnect Watchdog

Auto-reconnect Wi-Fi on drop. Useful for headless Raspberry Pi.

bash
#!/bin/bash
INTERFACE="${1:-wlan0}"
CHECK_HOST="8.8.8.8"
LOG="/var/log/wifi_watchdog.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

while true; do
    if ! ping -c 2 -W 3 "$CHECK_HOST" &>/dev/null; then
        log "No internet. Restarting $INTERFACE..."
        ip link set "$INTERFACE" down
        sleep 2
        ip link set "$INTERFACE" up
        sleep 10
        if ping -c 2 "$CHECK_HOST" &>/dev/null; then
            log "Reconnected on $INTERFACE."
        else
            log "Reconnect failed. Restarting networking service..."
            systemctl restart networking
        fi
    fi
    sleep 60
done

Project 20 — Service Watchdog with Auto-Restart

bash
#!/bin/bash
SERVICE="${1:?Usage: $0 <service_name>}"
MAX_RESTARTS=5
WINDOW=300
LOG="/var/log/${SERVICE}_watchdog.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

RESTARTS=0
WINDOW_START=$(date +%s)

while true; do
    if ! systemctl is-active --quiet "$SERVICE"; then
        NOW=$(date +%s)
        if (( NOW - WINDOW_START > WINDOW )); then
            RESTARTS=0
            WINDOW_START=$NOW
        fi
        if (( RESTARTS >= MAX_RESTARTS )); then
            log "CRITICAL: $SERVICE restarted $MAX_RESTARTS times in ${WINDOW}s. Giving up."
            exit 1
        fi
        log "ALERT: $SERVICE is down. Restarting... (attempt $((RESTARTS+1)))"
        systemctl start "$SERVICE"
        ((RESTARTS++))
        sleep $((RESTARTS * 10))
    fi
    sleep 30
done

Project 21 — Sensor CSV Logger (I2C — Raspberry Pi)

Log BME280 temperature/humidity/pressure to CSV using i2cget.

bash
#!/bin/bash
LOG="sensor_$(date +%Y%m%d).csv"
INTERVAL=10

if [ ! -f "$LOG" ]; then
    echo "timestamp,temp_c,humidity_pct,pressure_hpa" > "$LOG"
fi

read_bme280() {
    # Requires python3-smbus: sudo apt install python3-smbus
    python3 - << 'PY'
import smbus2, bme280
bus = smbus2.SMBus(1)
calibration = bme280.load_calibration_params(bus, 0x76)
data = bme280.sample(bus, 0x76, calibration)
print(f"{data.temperature:.2f},{data.humidity:.2f},{data.pressure:.2f}")
PY
}

echo "Logging to $LOG every ${INTERVAL}s. Ctrl+C to stop."
while true; do
    TS=$(date '+%Y-%m-%d %H:%M:%S')
    READING=$(read_bme280 2>/dev/null)
    if [ -n "$READING" ]; then
        echo "$TS,$READING" | tee -a "$LOG"
    else
        echo "$TS,ERROR,ERROR,ERROR" >> "$LOG"
        echo "[$TS] Sensor read failed."
    fi
    sleep "$INTERVAL"
done

Project 22 — SSH Batch Deployer

Deploy a script to multiple SBCs in parallel.

bash
#!/bin/bash
SCRIPT="${1:?Usage: $0 <script.sh> <hosts_file>}"
HOSTS_FILE="${2:?}"
SSH_USER="${3:-pi}"
SSH_KEY="${4:-$HOME/.ssh/id_rsa}"
LOG="deploy_$(date +%Y%m%d_%H%M%S).log"

deploy_to() {
    local HOST=$1
    echo "[$(date '+%H:%M:%S')] Deploying to $HOST..." | tee -a "$LOG"
    scp -i "$SSH_KEY" -o StrictHostKeyChecking=no "$SCRIPT" "$SSH_USER@$HOST:/tmp/deploy_script.sh" \
        && ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "$SSH_USER@$HOST" "bash /tmp/deploy_script.sh" \
        && echo "[OK] $HOST" >> "$LOG" \
        || echo "[FAIL] $HOST" >> "$LOG"
}

while IFS= read -r HOST; do
    [[ -z "$HOST" || "$HOST" == \#* ]] && continue
    deploy_to "$HOST" &
done < "$HOSTS_FILE"

wait
echo "Deployment complete. Results in $LOG"
grep -E "OK|FAIL" "$LOG"

Project 23 — Git-Based OTA Updater

Pull latest code and restart service on Raspberry Pi.

bash
#!/bin/bash
set -e
REPO_DIR="${1:?Usage: $0 <repo_dir> <service_name>}"
SERVICE="${2:?}"
LOG="/var/log/ota_update.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

cd "$REPO_DIR"
git fetch origin

LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/main)

if [ "$LOCAL" = "$REMOTE" ]; then
    log "Already up to date ($LOCAL)."
    exit 0
fi

log "Update available: $LOCAL → $REMOTE"
git pull origin main

if [ -f "requirements.txt" ]; then
    log "Installing Python dependencies..."
    pip install -r requirements.txt -q
fi

log "Restarting $SERVICE..."
systemctl restart "$SERVICE"
systemctl is-active --quiet "$SERVICE" \
    && log "Update complete. $SERVICE is running." \
    || { log "ERROR: $SERVICE failed to start after update."; exit 1; }

Project 24 — Process Memory Monitor

Alert when a process exceeds a memory threshold.

bash
#!/bin/bash
PROCESS="${1:?Usage: $0 <process_name> [limit_mb]}"
LIMIT_MB="${2:-500}"
INTERVAL=10
LOG="/var/log/mem_monitor.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

while true; do
    PID=$(pgrep -x "$PROCESS" | head -1)
    if [ -z "$PID" ]; then
        log "WARN: $PROCESS is not running."
        sleep "$INTERVAL"; continue
    fi
    MEM_KB=$(awk '/VmRSS/{print $2}' /proc/"$PID"/status 2>/dev/null)
    MEM_MB=$((MEM_KB / 1024))
    if [ "$MEM_MB" -ge "$LIMIT_MB" ]; then
        log "ALERT: $PROCESS (PID $PID) using ${MEM_MB}MB — limit ${LIMIT_MB}MB"
    else
        log "OK: $PROCESS uses ${MEM_MB}MB"
    fi
    sleep "$INTERVAL"
done

Project 25 — Bandwidth Monitor

Track real-time network throughput on a Raspberry Pi.

bash
#!/bin/bash
IFACE="${1:-eth0}"
INTERVAL="${2:-2}"

rx_bytes() { cat /sys/class/net/"$IFACE"/statistics/rx_bytes; }
tx_bytes() { cat /sys/class/net/"$IFACE"/statistics/tx_bytes; }

OLD_RX=$(rx_bytes); OLD_TX=$(tx_bytes)
echo "Monitoring $IFACE every ${INTERVAL}s (Ctrl+C to stop)"
printf "%-24s %12s %12s\n" "Timestamp" "Download" "Upload"

while true; do
    sleep "$INTERVAL"
    NEW_RX=$(rx_bytes); NEW_TX=$(tx_bytes)
    RX_RATE=$(( (NEW_RX - OLD_RX) * 8 / INTERVAL / 1024 ))
    TX_RATE=$(( (NEW_TX - OLD_TX) * 8 / INTERVAL / 1024 ))
    printf "%-24s %9s kbps %9s kbps\n" \
        "$(date '+%Y-%m-%d %H:%M:%S')" "$RX_RATE" "$TX_RATE"
    OLD_RX=$NEW_RX; OLD_TX=$NEW_TX
done

Project 26 — SD Card Health Checker

Check SD card health on Raspberry Pi before it fails.

bash
#!/bin/bash
DEVICE="${1:-/dev/mmcblk0}"
LOG="/var/log/sdcard_health.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }

log "=== SD Card Health Check ==="

# Check filesystem errors
log "Running fsck (read-only)..."
fsck -n "$DEVICE" 2>&1 | tail -5 | while read -r L; do log "$L"; done

# Check for I/O errors in dmesg
IO_ERRORS=$(dmesg | grep -c "I/O error" 2>/dev/null)
log "I/O errors in dmesg: $IO_ERRORS"

# Disk usage
log "Disk usage:"
df -h / | tail -1 | awk '{log=$5; print "  Used: "log}' || df -h /

# Bad blocks (WARNING: takes a long time on large cards)
# badblocks -v "$DEVICE"

log "Check complete."

Expert Projects (27–35)

Project 27 — First-Boot Provisioner (Raspberry Pi)

Run once on first boot to fully configure a fresh Pi.

bash
#!/bin/bash
set -euo pipefail
MARKER="/etc/.provisioned"
LOG="/var/log/provision.log"
exec > >(tee -a "$LOG") 2>&1

[ -f "$MARKER" ] && { echo "Already provisioned. Skipping."; exit 0; }

echo "=== First Boot Provisioning: $(date) ==="

# Set hostname
NEW_HOST="${HOSTNAME_PREFIX:-pi}-$(date +%s | tail -c 4)"
hostnamectl set-hostname "$NEW_HOST"
sed -i "s/127.0.1.1.*/127.0.1.1\t$NEW_HOST/" /etc/hosts
echo "Hostname set: $NEW_HOST"

# Update system
apt-get update -qq && apt-get upgrade -y

# Install base tools
apt-get install -y git python3 python3-pip python3-venv \
    i2c-tools build-essential vim htop curl

# Enable I2C and SPI
raspi-config nonint do_i2c 0
raspi-config nonint do_spi 0

# Create a project directory
mkdir -p /home/pi/projects
chown pi:pi /home/pi/projects

touch "$MARKER"
echo "=== Provisioning complete: $(date) ==="
reboot

Project 28 — Parallel SSH Command Runner

Run a command on a fleet of SBCs simultaneously.

bash
#!/bin/bash
CMD="${1:?Usage: $0 \"<command>\" <hosts_file>}"
HOSTS_FILE="${2:?}"
SSH_USER="${3:-pi}"
SSH_KEY="${4:-$HOME/.ssh/id_rsa}"
TIMEOUT=15

run_on() {
    local HOST=$1
    RESULT=$(ssh -i "$SSH_KEY" -o ConnectTimeout="$TIMEOUT" \
        -o StrictHostKeyChecking=no \
        "$SSH_USER@$HOST" "$CMD" 2>&1)
    printf "[%-20s] %s\n" "$HOST" "$RESULT"
}

export -f run_on
export SSH_KEY SSH_USER TIMEOUT CMD

grep -v '^\s*#' "$HOSTS_FILE" | grep -v '^\s*$' | \
    xargs -P 10 -I{} bash -c 'run_on "$@"' _ {}

echo "Done."

Usage: ./fleet_cmd.sh "uname -r" hosts.txt


Project 29 — Automated ESP32 Flasher

Build and flash ESP32 firmware automatically, detecting the port.

bash
#!/bin/bash
set -euo pipefail
TARGET="${1:-esp32}"
BAUD="${2:-460800}"

log()  { echo -e "\033[0;32m[INFO]\033[0m  $*"; }
err()  { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; exit 1; }

detect_port() {
    for P in /dev/ttyUSB* /dev/ttyACM*; do
        [ -c "$P" ] && { echo "$P"; return; }
    done
    err "No serial device found. Is ESP32 connected?"
}

PORT=$(detect_port)
log "Detected device: $PORT"
log "Target chip: $TARGET"

log "Setting target..."
idf.py set-target "$TARGET"

log "Building..."
idf.py build

log "Flashing to $PORT at $BAUD baud..."
idf.py -p "$PORT" -b "$BAUD" flash

log "Flash complete. Starting monitor..."
idf.py -p "$PORT" monitor

Project 30 — Log Anomaly Detector

Parse logs and flag lines matching suspicious patterns.

bash
#!/bin/bash
LOG_FILE="${1:-/var/log/syslog}"
RULES_FILE="${2:-/etc/anomaly_rules.txt}"
ALERT_LOG="/var/log/anomaly_alerts.log"

# Default rules if file doesn't exist
if [ ! -f "$RULES_FILE" ]; then
    cat << 'EOF' > "$RULES_FILE"
segfault
kernel panic
Out of memory
authentication failure
Permission denied
connection refused
disk full
read-only file system
EOF
fi

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$ALERT_LOG"; }

PATTERN=$(paste -sd'|' "$RULES_FILE")

log "=== Scanning $LOG_FILE ==="
grep -iE "$PATTERN" "$LOG_FILE" | tail -100 | while IFS= read -r LINE; do
    log "MATCH: $LINE"
done

TOTAL=$(grep -ciE "$PATTERN" "$LOG_FILE" 2>/dev/null || echo 0)
log "Total anomalous lines found: $TOTAL"

Project 31 — Camera Snapshot + Upload (Raspberry Pi)

Capture a frame and upload to a remote server.

bash
#!/bin/bash
DEST_HOST="${1:?Usage: $0 <user@host:/path>}"
INTERVAL="${2:-60}"
SNAP_DIR="/tmp/snapshots"
mkdir -p "$SNAP_DIR"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }

capture() {
    FILE="$SNAP_DIR/snap_$(date +%Y%m%d_%H%M%S).jpg"
    # Use libcamera-still on Pi 5 / newer OS:
    if command -v libcamera-still &>/dev/null; then
        libcamera-still -o "$FILE" --nopreview -t 200 2>/dev/null
    else
        # Legacy raspistill:
        raspistill -o "$FILE" -t 200 -n 2>/dev/null
    fi
    echo "$FILE"
}

while true; do
    FILE=$(capture)
    if [ -f "$FILE" ]; then
        log "Captured: $(basename "$FILE")"
        scp -o StrictHostKeyChecking=no "$FILE" "$DEST_HOST" \
            && { log "Uploaded OK."; rm "$FILE"; } \
            || log "Upload failed. File kept locally."
    else
        log "Capture failed."
    fi
    sleep "$INTERVAL"
done

Project 32 — dotfiles Installer

Set up a developer environment on any fresh Linux/SBC machine.

bash
#!/bin/bash
set -e
DOTFILES_REPO="${1:-https://github.com/youruser/dotfiles.git}"
DOTFILES_DIR="$HOME/.dotfiles"

log() { echo -e "\033[0;34m[SETUP]\033[0m $*"; }

# Clone dotfiles
if [ ! -d "$DOTFILES_DIR" ]; then
    log "Cloning dotfiles..."
    git clone "$DOTFILES_REPO" "$DOTFILES_DIR"
fi

# Link config files
for FILE in bashrc vimrc tmux.conf gitconfig; do
    SRC="$DOTFILES_DIR/$FILE"
    DEST="$HOME/.$FILE"
    [ -f "$SRC" ] || continue
    [ -f "$DEST" ] && mv "$DEST" "${DEST}.bak"
    ln -sf "$SRC" "$DEST"
    log "Linked: $DEST → $SRC"
done

# Install packages
log "Installing dev packages..."
sudo apt-get install -y vim tmux git curl htop tree jq build-essential python3-pip

# Install Oh My Bash (optional)
if [ ! -d "$HOME/.oh-my-bash" ]; then
    bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmybash/oh-my-bash/master/tools/install.sh)" \
        --unattended || true
fi

log "Setup complete. Reload your shell: source ~/.bashrc"

Project 33 — GPIO Button Event Handler

Detect a GPIO button press and run an action (Raspberry Pi).

bash
#!/bin/bash
# Wire: Button between GPIO 27 (pin 13) and GND
# Requires: internal pull-up is enabled via direction
GPIO_PIN="${1:-27}"
GPIO_PATH="/sys/class/gpio"
ACTION="${2:-echo BUTTON PRESSED}"

cleanup() { echo "$GPIO_PIN" > "$GPIO_PATH/unexport" 2>/dev/null; }
trap cleanup EXIT

echo "$GPIO_PIN" > "$GPIO_PATH/export" 2>/dev/null || true
sleep 0.1
echo "in" > "$GPIO_PATH/gpio${GPIO_PIN}/direction"
# Enable active-low (pull up)
echo 1 > "$GPIO_PATH/gpio${GPIO_PIN}/active_low" 2>/dev/null || true

echo "Waiting for button press on GPIO $GPIO_PIN..."
LAST=0
while true; do
    VAL=$(cat "$GPIO_PATH/gpio${GPIO_PIN}/value")
    if [ "$VAL" -eq 1 ] && [ "$LAST" -eq 0 ]; then
        echo "[$(date '+%H:%M:%S')] Button pressed!"
        eval "$ACTION"
    fi
    LAST=$VAL
    sleep 0.05
done

Project 34 — Secure Encrypted Backup

Create an AES-encrypted backup and upload offsite.

bash
#!/bin/bash
SOURCE="${1:?Usage: $0 <dir> <passphrase> [dest_host]}"
PASS="${2:?}"
DEST_HOST="${3:-}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
ARCHIVE="/tmp/backup_${TIMESTAMP}.tar.gz.enc"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }

log "Creating encrypted backup of $SOURCE..."
tar -czf - "$SOURCE" | \
    openssl enc -aes-256-cbc -salt -pbkdf2 -pass "pass:$PASS" \
    -out "$ARCHIVE"

SIZE=$(du -sh "$ARCHIVE" | cut -f1)
log "Encrypted archive: $ARCHIVE ($SIZE)"

if [ -n "$DEST_HOST" ]; then
    log "Uploading to $DEST_HOST..."
    scp "$ARCHIVE" "$DEST_HOST"
    rm "$ARCHIVE"
    log "Upload complete. Local copy removed."
fi

# Verify decryption works
log "Verifying decryption..."
openssl enc -d -aes-256-cbc -salt -pbkdf2 -pass "pass:$PASS" \
    -in "$ARCHIVE" 2>/dev/null | tar -tzf - > /dev/null \
    && log "Verification passed." \
    || log "WARNING: Verification failed!"

Project 35 — Full SBC Health Dashboard

Print a live terminal dashboard for any Raspberry Pi or SBC.

bash
#!/bin/bash
IFACE="${1:-eth0}"
INTERVAL="${2:-2}"

# Save/restore terminal
tput smcup; tput civis
trap 'tput rmcup; tput cnorm; echo ""; exit 0' INT TERM

OLD_RX=$(cat /sys/class/net/"$IFACE"/statistics/rx_bytes 2>/dev/null || echo 0)
OLD_TX=$(cat /sys/class/net/"$IFACE"/statistics/tx_bytes 2>/dev/null || echo 0)

while true; do
    tput cup 0 0
    W=$(tput cols)
    SEP=$(printf '%*s' "$W" '' | tr ' ' '─')

    TEMP_RAW=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0)
    TEMP=$(awk "BEGIN{printf \"%.1f\", $TEMP_RAW/1000}")
    CPU=$(top -bn1 | awk '/Cpu/ {printf "%.1f", 100-$8}')
    MEM=$(free | awk '/Mem/{printf "%.1f", $3/$2*100}')
    DISK=$(df / | awk 'NR==2{print $5}')
    UPTIME=$(uptime -p | sed 's/up //')
    IP=$(hostname -I | awk '{print $1}')

    NEW_RX=$(cat /sys/class/net/"$IFACE"/statistics/rx_bytes 2>/dev/null || echo 0)
    NEW_TX=$(cat /sys/class/net/"$IFACE"/statistics/tx_bytes 2>/dev/null || echo 0)
    RX_K=$(( (NEW_RX - OLD_RX) * 8 / INTERVAL / 1024 ))
    TX_K=$(( (NEW_TX - OLD_TX) * 8 / INTERVAL / 1024 ))
    OLD_RX=$NEW_RX; OLD_TX=$NEW_TX

    echo -e "\033[1;36m$(hostname) — SBC Health Dashboard\033[0m  $(date '+%Y-%m-%d %H:%M:%S')"
    echo "$SEP"
    printf " %-14s \033[1;33m%s°C\033[0m\n"  "CPU Temp:"    "$TEMP"
    printf " %-14s \033[1;32m%s%%\033[0m\n"  "CPU Usage:"   "$CPU"
    printf " %-14s \033[1;32m%s%%\033[0m\n"  "Memory:"      "$MEM"
    printf " %-14s \033[1;32m%s\033[0m\n"    "Disk (/):"    "$DISK"
    printf " %-14s \033[1;37m%s\033[0m\n"    "Uptime:"      "$UPTIME"
    printf " %-14s \033[1;37m%s\033[0m\n"    "IP:"          "$IP"
    echo "$SEP"
    printf " %-14s ↓ %s kbps   ↑ %s kbps\n" "$IFACE:"  "$RX_K" "$TX_K"
    echo "$SEP"
    echo " Top Processes:"
    ps aux --sort=-%cpu --no-headers | head -5 | \
        awk '{printf "  %-20s CPU:%-6s MEM:%s%%\n", $11, $3, $4}'
    echo "$SEP"

    sleep "$INTERVAL"
done

Project Index

#ProjectLevelKey Skills
1Hello SystemBeginnerecho, hostname, date
2Directory OrganiserBeginnerfor, mv, extension parsing
3Timestamped BackupBeginnertar, date, mkdir
4CalculatorBeginnercase, bc, read
5Command CheckerBeginnerarrays, command -v
6Countdown TimerBeginnerwhile, printf, sleep
7Large File FinderBeginnerfind, sort, awk
8User ReporterBeginnerawk, who, last
9CPU Temp MonitorIntermediate/sys, while, logging
10Disk Space AlertIntermediatedf, awk, threshold
11HTML System ReportIntermediateheredoc, HTML, lscpu
12Auto UpdaterIntermediateapt, cron, reboot
13Log RotatorIntermediatefind, gzip, mtime
14Network MonitorIntermediateping, assoc arrays
15Batch RenamerIntermediatefind, string replace
16Menu SystemIntermediatecase, tput, UI
17GPIO LED BlinkerIntermediate/sys/class/gpio, trap
18Serial LoggerIntermediatestty, read, /dev
19Wi-Fi WatchdogAdvancedip, systemctl, ping
20Service WatchdogAdvancedsystemctl, rate limiting
21Sensor CSV LoggerAdvancedI2C, python3, CSV
22SSH Batch DeployerAdvancedscp, ssh, xargs -P
23Git OTA UpdaterAdvancedgit, systemctl, pip
24Memory MonitorAdvanced/proc, pgrep
25Bandwidth MonitorAdvanced/sys/net, arithmetic
26SD Card CheckerAdvancedfsck, dmesg, badblocks
27First-Boot ProvisionerExpertraspi-config, systemd
28Fleet SSH RunnerExpertxargs -P, ssh, export
29ESP32 FlasherExpertidf.py, detect port
30Log Anomaly DetectorExpertgrep -E, rules file
31Camera + UploadExpertlibcamera-still, scp
32dotfiles InstallerExpertsymlinks, git, Oh-My-Bash
33GPIO Button HandlerExpert/sys/class/gpio, event loop
34Encrypted BackupExpertopenssl, aes-256, scp
35SBC DashboardExperttput, /sys, live UI
Previous
Advanced Shell Scripting