Guide Bash-Scripting Advanced

Bash Projects

Five practical, portfolio-ready Bash projects that combine everything from this series — a server health checker, log analyzer, backup tool, deploy script, and AWS resource auditor.

4 min read

Project 1: Server Health Checker

Combines: functions, conditions, exit codes, file handling.

#!/usr/bin/env bash
set -euo pipefail

check_disk() {
    local usage
    usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
    [ "$usage" -lt 90 ] && echo "OK: Disk usage ${usage}%" || { echo "CRITICAL: Disk usage ${usage}%" >&2; return 1; }
}

check_memory() {
    local available
    available=$(free | awk '/Mem/ {printf "%.0f", $7/$2 * 100}')
    [ "$available" -gt 10 ] && echo "OK: ${available}% memory available" || { echo "CRITICAL: only ${available}% memory available" >&2; return 1; }
}

check_load() {
    local load cores
    load=$(uptime | awk -F'load average:' '{print $2}' | awk -F, '{print $1}' | tr -d ' ')
    cores=$(nproc)
    echo "Load: $load (cores: $cores)"
}

main() {
    local failed=0
    check_disk || failed=1
    check_memory || failed=1
    check_load
    exit "$failed"
}
main "$@"

Project 2: Log Analyzer With Alerting

Combines: text processing, log processing, networking automation.

#!/usr/bin/env bash
set -euo pipefail

LOGFILE="${1:?Usage: $0 <logfile> [threshold]}"
THRESHOLD="${2:-50}"

ERROR_COUNT=$(grep -c "ERROR" "$LOGFILE" || true)
echo "Errors found: $ERROR_COUNT (threshold: $THRESHOLD)"

if [ "$ERROR_COUNT" -gt "$THRESHOLD" ]; then
    echo "=== Top error types ==="
    grep "ERROR" "$LOGFILE" | awk -F'ERROR: ' '{print $2}' | sort | uniq -c | sort -rn | head -5
    [ -n "${WEBHOOK_URL:-}" ] && curl -sf -X POST -H 'Content-Type: application/json' \
        -d "{\"text\":\"Alert: $ERROR_COUNT errors in $LOGFILE\"}" "$WEBHOOK_URL"
    exit 1
fi

Project 3: Automated Backup Tool

Combines: file handling, archiving, cron scheduling, error handling.

#!/usr/bin/env bash
set -euo pipefail

SOURCE_DIR="${1:?Usage: $0 <source_dir> <dest_dir>}"
DEST_DIR="${2:?Usage: $0 <source_dir> <dest_dir>}"
DATE=$(date +%Y%m%d-%H%M%S)
ARCHIVE="$DEST_DIR/backup-$DATE.tar.gz"
RETENTION_DAYS=30

mkdir -p "$DEST_DIR"
tar -czf "$ARCHIVE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
echo "Backup created: $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"

find "$DEST_DIR" -name "backup-*.tar.gz" -mtime "+$RETENTION_DAYS" -print -delete
echo "Cleaned up backups older than $RETENTION_DAYS days"
0 2 * * * /usr/bin/bash /opt/scripts/backup.sh /data /backups >> /var/log/backup.log 2>&1

Project 4: Zero-Downtime Deploy Script

Combines: SSH automation, signals & trap, functions, exit codes.

#!/usr/bin/env bash
set -euo pipefail

SERVERS=("web1.example.com" "web2.example.com" "web3.example.com")
APP_DIR="/opt/myapp"
FAILED_SERVERS=()

deploy_to_server() {
    local server="$1"
    ssh -o BatchMode=yes "deploy@$server" bash -s << EOF
        set -e
        cd "$APP_DIR"
        git pull origin main
        systemctl restart myapp
        sleep 3
        systemctl is-active --quiet myapp
EOF
}

for server in "${SERVERS[@]}"; do
    echo "Deploying to $server..."
    if deploy_to_server "$server"; then
        echo "$server: OK"
    else
        echo "$server: FAILED" >&2
        FAILED_SERVERS+=("$server")
    fi
done

if [ "${#FAILED_SERVERS[@]}" -gt 0 ]; then
    echo "Deployment failed on: ${FAILED_SERVERS[*]}" >&2
    exit 1
fi
echo "Deployed successfully to all ${#SERVERS[@]} servers"

Project 5: AWS Resource Auditor

Combines: Bash + AWS CLI, arrays, text processing, production-grade structure.

#!/usr/bin/env bash
set -euo pipefail

REGION="${1:-us-east-1}"

echo "=== Unattached EBS Volumes (potential cost savings) ==="
aws ec2 describe-volumes --region "$REGION" --filters "Name=status,Values=available" \
    --query 'Volumes[*].[VolumeId,Size,CreateTime]' --output table

echo "=== Untagged Running Instances ==="
aws ec2 describe-instances --region "$REGION" \
    --filters "Name=instance-state-name,Values=running" \
    --query "Reservations[].Instances[?!not_null(Tags[?Key=='Environment'])].[InstanceId,InstanceType]" \
    --output table

echo "=== S3 Buckets Without Versioning Enabled ==="
for bucket in $(aws s3api list-buckets --query 'Buckets[*].Name' --output text); do
    STATUS=$(aws s3api get-bucket-versioning --bucket "$bucket" --query 'Status' --output text 2>/dev/null || echo "None")
    [ "$STATUS" != "Enabled" ] && echo "$bucket: $STATUS"
done

What Each Project Teaches

ProjectCore Skills Combined
Health CheckerFunctions, conditions, exit codes
Log AnalyzerText processing (awk/grep), alerting, thresholds
Backup ToolFile handling, tar, find, cron scheduling
Deploy ScriptSSH automation, arrays, error tracking, heredocs
AWS AuditorAWS CLI, JMESPath queries, loops over API results

Production Considerations

  • Each of these is a realistic starting point, not a finished tool — add set -euo pipefail, trap cleanup, structured logging, and a DRY_RUN mode (per the Production-Grade Bash tutorial) before trusting any of them with real infrastructure.
  • Version-control these scripts alongside the infrastructure they manage, and run shellcheck on them in CI just like application code.
  • Start simple, then layer in error handling and observability incrementally — a working, readable 30-line script beats an overengineered 300-line one that’s never actually tested.

Quick Interview Answer

“A strong portfolio project doesn’t need to be exotic — a server health checker, a log analyzer with alerting, an automated backup tool, a rolling multi-server deploy script, and an AWS resource auditor each combine 3-4 core Bash concepts (functions, error handling, text processing, SSH/API automation) into something genuinely useful. What makes them production-grade rather than a toy is set -euo pipefail, proper cleanup via trap, and structured logging — not more features.”

Common Mistakes

  • Building an impressive-looking script that skips basic error handling (set -euo pipefail) entirely.
  • Not testing failure paths (what happens when a server is unreachable, or df/aws output is unexpected) — only the happy path.
  • Treating a “project” as a one-off script instead of something maintained, version-controlled, and reviewed like any other code.

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form