5 Real-World Shell Scripting Projects: From System Info to Monitoring
Theory gets you started. Projects make you proficient. In this chapter, we bring together everything — variables, conditionals, loops, functions, file operations, process management, and error handling — into five complete, production-ready scripts that solve real problems.
Each project is self-contained, well-commented, and follows best practices: strict mode, input validation, logging, and cleanup. You can run these scripts as-is or adapt them to your own infrastructure.
Project 1: System Information Script
A quick diagnostic tool that gathers and displays key system metrics. Perfect for onboarding new servers, troubleshooting, or including in automated reports.
1#!/bin/bash 2 3set -euo pipefail 4 5# ─── Gather System Data ─── 6HOSTNAME=$(hostname) 7OS=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2) 8KERNEL=$(uname -r) 9UPTIME=$(uptime -p 2>/dev/null || uptime | awk -F',' '{print $1}') 10CPU=$(grep -m1 'model name' /proc/cpuinfo | cut -d':' -f2 | xargs) 11CPU_CORES=$(nproc) 12MEM_TOTAL=$(free -h | awk '/^Mem:/ {print $2}') 13MEM_USED=$(free -h | awk '/^Mem:/ {print $3}') 14MEM_PERCENT=$(free | awk '/^Mem:/ {printf "%.1f", $3/$2 * 100}') 15DISK_USAGE=$(df -h / | awk 'NR==2 {print $5 " (" $3 " used of " $2 ")"}') 16IP=$(hostname -I | awk '{print $1}') 17LOAD=$(uptime | awk -F'load average:' '{print $2}' | xargs) 18 19# ─── Display Report ─── 20cat << EOF 21 22╔══════════════════════════════════════════╗ 23║ SYSTEM INFORMATION REPORT ║ 24╠══════════════════════════════════════════╣ 25 Hostname : $HOSTNAME 26 OS : $OS 27 Kernel : $KERNEL 28 Uptime : $UPTIME 29 IP Address : $IP 30 Load Avg : $LOAD 31────────────────────────────────────────── 32 CPU : $CPU 33 CPU Cores : $CPU_CORES 34 Memory : $MEM_USED / $MEM_TOTAL ($MEM_PERCENT%) 35 Disk (/) : $DISK_USAGE 36╚══════════════════════════════════════════╝ 37 38EOF
What it demonstrates:
- Command substitution for system data gathering
awkfor parsing command output- Here documents for formatted output
- Error suppression with
2>/dev/null
Usage:
1chmod +x sysinfo.sh 2./sysinfo.sh
Project 2: Log Analyzer
Parses application or system logs and generates a summary report with error counts, top error patterns, and hourly activity distribution.
1#!/bin/bash 2 3set -euo pipefail 4 5LOG_FILE="${1:-/var/log/syslog}" 6REPORT_FILE="log_report_$(date +%Y%m%d_%H%M%S).txt" 7 8if [ ! -f "$LOG_FILE" ]; then 9 echo "Error: Log file not found: $LOG_FILE" >&2 10 exit 1 11fi 12 13echo "Analyzing: $LOG_FILE" 14echo "This may take a moment..." 15 16{ 17 echo "===================================" 18 echo " LOG ANALYSIS REPORT " 19 echo "===================================" 20 echo "Source: $LOG_FILE" 21 echo "Generated: $(date)" 22 echo "" 23 24 # Total lines 25 total_lines=$(wc -l < "$LOG_FILE") 26 echo "Total Lines: $total_lines" 27 28 # Error and warning counts 29 error_count=$(grep -ci "error" "$LOG_FILE" || true) 30 warn_count=$(grep -ci "warn" "$LOG_FILE" || true) 31 info_count=$(grep -ci "info" "$LOG_FILE" || true) 32 33 echo "" 34 echo "Severity Distribution:" 35 echo " Errors: $error_count" 36 echo " Warnings: $warn_count" 37 echo " Info: $info_count" 38 39 # Top 10 error messages 40 echo "" 41 echo "Top 10 Error Patterns:" 42 grep -i "error" "$LOG_FILE" | sed 's/.*ERROR: //i; s/.*error: //i' | sort | uniq -c | sort -rn | head -n 10 || echo " No errors found." 43 44 # Top 10 IP addresses (if applicable) 45 echo "" 46 echo "Top 10 IP Addresses:" 47 grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' "$LOG_FILE" | sort | uniq -c | sort -rn | head -n 10 || echo " No IP addresses found." 48 49 # Hourly activity distribution 50 echo "" 51 echo "Hourly Activity Distribution:" 52 awk '{print substr($0, 1, 15)}' "$LOG_FILE" | awk -F':' '{print $1":"$2}' | sort | uniq -c | sort -rn | head -n 10 53 54 echo "" 55 echo "===================================" 56} > "$REPORT_FILE" 57 58echo "Report saved to: $REPORT_FILE"
What it demonstrates:
- File existence validation
- Counting and filtering with
grep - Text extraction with
sedand regex - Sorting, counting, and ranking with
sort | uniq -c | sort -rn - Report generation with redirection
Usage:
1chmod +x log_analyzer.sh 2./log_analyzer.sh /var/log/nginx/access.log
Project 3: Backup Script with Rotation
A robust backup utility that compresses a directory, logs the operation, and automatically deletes backups older than a retention period.
1#!/bin/bash 2 3set -euo pipefail 4 5# ─── Configuration ─── 6SOURCE_DIR="${1:-/var/www/html}" 7BACKUP_DIR="${2:-/backups}" 8RETENTION_DAYS=7 9DATE=$(date +%Y%m%d_%H%M%S) 10BACKUP_NAME="backup_$(basename "$SOURCE_DIR")_${DATE}.tar.gz" 11LOG_FILE="$BACKUP_DIR/backup.log" 12 13# ─── Functions ─── 14log() { 15 echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" 16} 17 18cleanup_old_backups() { 19 log "Cleaning up backups older than $RETENTION_DAYS days..." 20 find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +"$RETENTION_DAYS" -delete 21 log "Cleanup complete." 22} 23 24# ─── Main ─── 25main() { 26 log "=== Backup Started ===" 27 log "Source: $SOURCE_DIR" 28 log "Destination: $BACKUP_DIR" 29 30 # Validate source 31 if [ ! -d "$SOURCE_DIR" ]; then 32 log "ERROR: Source directory does not exist: $SOURCE_DIR" 33 exit 1 34 fi 35 36 # Ensure backup directory exists 37 mkdir -p "$BACKUP_DIR" 38 39 # Create backup 40 local backup_path="$BACKUP_DIR/$BACKUP_NAME" 41 42 if tar -czf "$backup_path" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"; then 43 local size 44 size=$(du -h "$backup_path" | cut -f1) 45 log "SUCCESS: Backup created: $BACKUP_NAME ($size)" 46 else 47 log "ERROR: Backup failed!" 48 rm -f "$backup_path" 49 exit 1 50 fi 51 52 # Cleanup old backups 53 cleanup_old_backups 54 55 log "=== Backup Completed ===" 56} 57 58# Trap for cleanup on interruption 59trap 'log "Backup interrupted!"' INT TERM 60 61main
What it demonstrates:
- Argument defaults (
${1:-default}) - Logging to file with timestamps
tarwith-Cfor clean archive pathsfindwith-mtimefor retention policies- Trap for graceful interruption handling
Usage:
1chmod +x backup.sh 2sudo ./backup.sh /etc /backups/system
Project 4: User Management Script
A CLI tool for system administrators to add, delete, list, lock, and unlock user accounts — with root privilege checks and confirmation prompts.
1#!/bin/bash 2 3set -euo pipefail 4 5SCRIPT_NAME=$(basename "$0") 6 7usage() { 8 cat << EOF 9Usage: $SCRIPT_NAME {add|delete|list|lock|unlock} [username] 10 11Commands: 12 add <username> Create a new user with home directory 13 delete <username> Remove a user and their home directory 14 list Show all regular users 15 lock <username> Disable user login 16 unlock <username> Re-enable user login 17EOF 18} 19 20check_root() { 21 if [ "$EUID" -ne 0 ]; then 22 echo "Error: This command must be run as root." >&2 23 exit 1 24 fi 25} 26 27add_user() { 28 local username=$1 29 30 if id "$username" &>/dev/null; then 31 echo "Error: User '$username' already exists." >&2 32 return 1 33 fi 34 35 read -sp "Enter password for $username: " password 36 echo "" 37 38 useradd -m -s /bin/bash "$username" 39 echo "$username:$password" | chpasswd 40 echo "User '$username' created successfully." 41} 42 43delete_user() { 44 local username=$1 45 46 if ! id "$username" &>/dev/null; then 47 echo "Error: User '$username' does not exist." >&2 48 return 1 49 fi 50 51 read -p "Are you sure you want to delete '$username'? [y/N]: " confirm 52 if [[ "$confirm" =~ ^[Yy]$ ]]; then 53 userdel -r "$username" 54 echo "User '$username' deleted." 55 else 56 echo "Cancelled." 57 fi 58} 59 60list_users() { 61 echo "Regular users (UID >= 1000):" 62 awk -F: '$3 >= 1000 && $3 != 65534 {print " " $1 " (UID: " $3 ")"}' /etc/passwd 63} 64 65lock_user() { 66 local username=$1 67 usermod -L "$username" 68 echo "User '$username' has been locked." 69} 70 71unlock_user() { 72 local username=$1 73 usermod -U "$username" 74 echo "User '$username' has been unlocked." 75} 76 77# ─── Main ─── 78case "${1:-}" in 79 add) 80 check_root 81 [ -z "${2:-}" ] && { usage; exit 1; } 82 add_user "$2" 83 ;; 84 delete) 85 check_root 86 [ -z "${2:-}" ] && { usage; exit 1; } 87 delete_user "$2" 88 ;; 89 list) 90 list_users 91 ;; 92 lock) 93 check_root 94 [ -z "${2:-}" ] && { usage; exit 1; } 95 lock_user "$2" 96 ;; 97 unlock) 98 check_root 99 [ -z "${2:-}" ] && { usage; exit 1; } 100 unlock_user "$2" 101 ;; 102 *) 103 usage 104 exit 1 105 ;; 106esac
What it demonstrates:
- Subcommand pattern with
casestatements - Root privilege validation with
$EUID - Secure password input with
read -sp - User existence checks with
id awkfor parsing/etc/passwd
Usage:
1chmod +x user_manager.sh 2sudo ./user_manager.sh add alice 3sudo ./user_manager.sh list 4sudo ./user_manager.sh delete alice
Project 5: Website Health Monitor
A continuous monitoring script that checks website availability, measures response times, and logs status — with optional email alerts.
1#!/bin/bash 2 3set -euo pipefail 4 5# ─── Configuration ─── 6URLS_FILE="${1:-urls.txt}" 7INTERVAL="${2:-60}" 8LOG_FILE="health_monitor.log" 9TIMEOUT=10 10 11# Colors for terminal output 12RED='\033[0;31m' 13GREEN='\033[0;32m' 14YELLOW='\033[1;33m' 15NC='\033[0m' # No Color 16 17# ─── Functions ─── 18log() { 19 echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" 20} 21 22check_url() { 23 local url=$1 24 local status_code 25 local response_time 26 27 status_code=$(curl -o /dev/null -s -w "%{http_code}" --max-time "$TIMEOUT" "$url" || echo "000") 28 response_time=$(curl -o /dev/null -s -w "%{time_total}" --max-time "$TIMEOUT" "$url" || echo "0") 29 30 if [ "$status_code" -eq 200 ]; then 31 echo -e "${GREEN}✓${NC} $url | Status: $status_code | Time: ${response_time}s" 32 log "OK: $url (HTTP $status_code, ${response_time}s)" 33 elif [ "$status_code" -ge 500 ]; then 34 echo -e "${RED}✗${NC} $url | Status: $status_code | Server Error!" 35 log "CRITICAL: $url returned HTTP $status_code" 36 elif [ "$status_code" -eq 000 ]; then 37 echo -e "${YELLOW}⚠${NC} $url | Timeout/Unreachable" 38 log "TIMEOUT: $url did not respond within ${TIMEOUT}s" 39 else 40 echo -e "${YELLOW}⚠${NC} $url | Status: $status_code" 41 log "WARNING: $url returned HTTP $status_code" 42 fi 43} 44 45# ─── Validate Input ─── 46if [ ! -f "$URLS_FILE" ]; then 47 echo "Error: URLs file not found: $URLS_FILE" >&2 48 echo "Creating sample urls.txt..." 49 cat > urls.txt << 'EOF' 50https://google.com 51https://github.com 52https://example.com 53EOF 54 echo "Please edit urls.txt and run again." 55 exit 1 56fi 57 58echo "=== Website Health Monitor ===" 59echo "URLs file: $URLS_FILE" 60echo "Check interval: ${INTERVAL}s" 61echo "Press Ctrl+C to stop" 62echo "" 63 64# ─── Main Loop ─── 65while true; do 66 echo "--- Check at $(date '+%H:%M:%S') ---" 67 68 while IFS= read -r url; do 69 [ -z "$url" ] && continue 70 [[ "$url" =~ ^# ]] && continue 71 check_url "$url" 72 done < "$URLS_FILE" 73 74 echo "" 75 sleep "$INTERVAL" 76done
What it demonstrates:
- Infinite loops with
while true curlfor HTTP health checks with timeouts- Color-coded terminal output with ANSI escape codes
- File-based configuration (URL lists)
- Comment and blank line filtering
Usage:
1# Create a URLs file 2cat > urls.txt << 'EOF' 3https://your-api.com/health 4https://your-website.com 5EOF 6 7chmod +x health_monitor.sh 8./health_monitor.sh urls.txt 30
What You've Built
Together, these five projects cover the full spectrum of shell scripting applications:
| Project | Skills Practiced |
|---|---|
| System Info | Command substitution, awk, formatting |
| Log Analyzer | File I/O, text processing, reporting |
| Backup Script | Compression, retention, logging, traps |
| User Manager | Privilege checks, subcommands, security |
| Health Monitor | HTTP requests, loops, color output, daemons |
Tips for Extending These Projects
- System Info: Add JSON output with
jqfor API consumption, or schedule it withcronto email daily reports. - Log Analyzer: Add real-time mode with
tail -fand integrate with Slack or PagerDuty webhooks. - Backup Script: Add S3 upload with
aws s3 cp, encryption withgpg, or parallel compression withpigz. - User Manager: Add group assignment, SSH key deployment, or integration with LDAP/Active Directory.
- Health Monitor: Add Prometheus metrics export, database connectivity checks, or certificate expiration alerts.
Best Practices Applied Across All Projects
set -euo pipefailon every script- Input validation before acting on files or commands
- Functions for reusable logic
- Logging with timestamps for audit trails
- Error messages to stderr (
>&2) to keep stdout clean - Cleanup traps for resource management
- Quoted variables to handle spaces and special characters
These projects are your starting point, not your endpoint. Take them apart, modify them, break them, and rebuild them. That's how you truly learn shell scripting — by solving real problems with real code.
Congratulations on completing this shell scripting series. You now have the skills to automate, monitor, and manage systems like a pro. Happy scripting!