DevOps

Bash Script Examples

analyse-logs.sh — Log Analysis Tool

This script analyses an Nginx (or Apache) access log to extract useful metrics: top IPs, HTTP status distribution, most-requested URLs, and error detection.

#!/usr/bin/env bash
# analyse-logs.sh — Nginx/Apache access log analyser
set -euo pipefail

# ─── Config ──────────────────────────────────────────────
LOG_FILE="${1:-/var/log/nginx/access.log}"
TOP_N="${2:-10}"

# ─── Colours ─────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'

# ─── Helpers ─────────────────────────────────────────────
log()     { echo -e "${GREEN}[INFO]${RESET}  $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET}  $*"; }
error()   { echo -e "${RED}[ERROR]${RESET} $*" >&2; exit 1; }
section() { echo -e "\n${BOLD}${CYAN}══ $* ══${RESET}"; }

# ─── Validate input ──────────────────────────────────────
[[ -f "$LOG_FILE" ]] || error "Log file not found: $LOG_FILE"
[[ -r "$LOG_FILE" ]] || error "Log file not readable: $LOG_FILE"

TOTAL=$(wc -l < "$LOG_FILE")
[[ $TOTAL -gt 0 ]] || error "Log file is empty"

log "Analysing: $LOG_FILE ($TOTAL entries)"

# ─── Total requests ──────────────────────────────────────
section "Summary"
echo "  Total requests : $TOTAL"

# Date range from first and last line
FIRST_DATE=$(head -1 "$LOG_FILE" | awk '{print $4}' | tr -d '[')
LAST_DATE=$(tail -1  "$LOG_FILE" | awk '{print $4}' | tr -d '[')
echo "  First request  : $FIRST_DATE"
echo "  Last request   : $LAST_DATE"

# ─── HTTP status distribution ────────────────────────────
section "HTTP Status Codes"
awk '{print $9}' "$LOG_FILE" \
  | grep -E '^[0-9]{3}$' \
  | sort \
  | uniq -c \
  | sort -rn \
  | awk '{ printf "  %-6s  %s\n", $2, $1 }'

# ─── Top IPs ─────────────────────────────────────────────
section "Top $TOP_N Client IPs"
awk '{print $1}' "$LOG_FILE" \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -"$TOP_N" \
  | awk '{ printf "  %-6s  %s\n", $1, $2 }'

# ─── Top URLs ────────────────────────────────────────────
section "Top $TOP_N Requested URLs"
awk '{print $7}' "$LOG_FILE" \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -"$TOP_N" \
  | awk '{ printf "  %-6s  %s\n", $1, $2 }'

# ─── 4xx/5xx errors ──────────────────────────────────────
section "Error Requests (4xx/5xx)"
ERROR_COUNT=$(awk '$9 ~ /^[45]/' "$LOG_FILE" | wc -l)
echo "  Total errors: $ERROR_COUNT"

if [[ $ERROR_COUNT -gt 0 ]]; then
    echo ""
    echo "  Top error URLs:"
    awk '$9 ~ /^[45]/ {print $9, $7}' "$LOG_FILE" \
      | sort \
      | uniq -c \
      | sort -rn \
      | head -5 \
      | awk '{ printf "    %-8s %-6s %s\n", $1, $2, $3 }'
fi

# ─── Bandwidth (if size field available) ─────────────────
section "Bandwidth"
awk '$10 ~ /^[0-9]+$/ {sum += $10} END {
    if (sum > 1073741824)
        printf "  Total: %.2f GB\n", sum/1073741824
    else if (sum > 1048576)
        printf "  Total: %.2f MB\n", sum/1048576
    else
        printf "  Total: %.2f KB\n", sum/1024
}' "$LOG_FILE"

log "Done."

How to Use It

# Make executable
chmod +x analyse-logs.sh

# Analyse default nginx log
./analyse-logs.sh

# Analyse a specific log
./analyse-logs.sh /var/log/apache2/access.log

# Show top 20 instead of top 10
./analyse-logs.sh /var/log/nginx/access.log 20

How It Works

SectionTechnique
Status codesawk '{print $9}' extracts field 9 (status), then `sort
Top IPsawk '{print $1}' extracts the client IP (field 1)
Top URLsawk '{print $7}' extracts the request path (field 7)
Errorsawk '$9 ~ /^[45]/' filters lines where status starts with 4 or 5
Bandwidthawk '$10 ~ /^[0-9]+$/' safely sums the byte count field

backup.sh — Simple Directory Backup

#!/usr/bin/env bash
# backup.sh — Create timestamped compressed backup
set -euo pipefail

SOURCE="${1:?Usage: backup.sh <source-dir> [dest-dir]}"
DEST="${2:-/backup}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_$(basename "$SOURCE")_${TIMESTAMP}.tar.gz"

mkdir -p "$DEST"

echo "Backing up: $SOURCE$DEST/$BACKUP_NAME"
tar -czf "$DEST/$BACKUP_NAME" -C "$(dirname "$SOURCE")" "$(basename "$SOURCE")"

SIZE=$(du -sh "$DEST/$BACKUP_NAME" | cut -f1)
echo "Done. Archive size: $SIZE"

# Keep only last 7 backups
BACKUP_BASE="backup_$(basename "$SOURCE")_"
ls -t "$DEST"/${BACKUP_BASE}*.tar.gz 2>/dev/null | tail -n +8 | xargs -r rm --
echo "Old backups pruned (kept last 7)."

health-check.sh — Service Health Monitor

#!/usr/bin/env bash
# health-check.sh — Check multiple services and alert on failure
set -euo pipefail

SERVICES=("nginx" "postgresql" "redis")
ALERT_EMAIL="${ALERT_EMAIL:-ops@example.com}"
FAILED=()

check_service() {
    local svc="$1"
    if systemctl is-active --quiet "$svc"; then
        echo "  [OK]   $svc"
    else
        echo "  [FAIL] $svc"
        FAILED+=("$svc")
    fi
}

echo "=== Service Health Check — $(date) ==="
for svc in "${SERVICES[@]}"; do
    check_service "$svc"
done

if [[ ${#FAILED[@]} -gt 0 ]]; then
    echo ""
    echo "ALERT: ${#FAILED[@]} service(s) down: ${FAILED[*]}"
    # Optionally send email
    # echo "Services down: ${FAILED[*]}" | mail -s "Service Alert" "$ALERT_EMAIL"
    exit 1
fi

echo ""
echo "All services healthy."

deploy.sh — Simple Zero-Downtime Deploy Pattern

#!/usr/bin/env bash
# deploy.sh — Pull latest, build, and reload
set -euo pipefail

APP_DIR="/opt/myapp"
REPO="git@github.com:org/myapp.git"
BRANCH="${1:-main}"

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

log "Deploying branch: $BRANCH"

cd "$APP_DIR"

log "Pulling latest..."
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"

log "Installing dependencies..."
npm ci --production

log "Building..."
npm run build

log "Reloading service (zero-downtime)..."
systemctl reload myapp.service

log "Deploy complete."