Shell Scripting Bash Loops Basics May 2026

Shell Scripting Loops: until & select

Master the until loop for condition-inverted polling, the select loop for building interactive terminal menus, nested loop patterns, and real-world examples like service wait loops and admin menu scripts.

While while loops run as long as a condition is true, until loops run as long as a condition is false — perfect for waiting until something becomes ready. The select loop generates numbered menus automatically, making it ideal for interactive admin scripts.

until is the logical inverse of while. The body runs as long as the condition evaluates to false (non-zero exit). Think of it as "keep trying until this succeeds."

BASH
# ── Basic until ───────────────────────────────────────────
count=1
until (( count > 5 )); do
  echo "  count = ${count}"
  (( count++ ))
done

# ── Wait until service is up ──────────────────────────────
echo "Waiting for MySQL to accept connections..."
until mysql -h "${DB_HOST}" -u root -e "SELECT 1" >/dev/null 2>&1; do
  echo "  Not ready yet — retrying in 3s..."
  sleep 3
done
echo "  ✔ MySQL is ready"

# ── Wait with timeout ─────────────────────────────────────
timeout=60
elapsed=0

until curl -sf "http://localhost:8080/health" >/dev/null; do
  if (( elapsed >= timeout )); then
    echo "ERROR: Service did not start within ${timeout}s" >&2
    exit 1
  fi
  echo "  Waiting... ${elapsed}s / ${timeout}s"
  sleep 2
  (( elapsed += 2 ))
done
echo "  ✔ Service ready after ${elapsed}s"
Key Service ready Waiting / retrying Timeout / error
bash — wait_for_mysql.sh
vriddh@prod-01:~/scripts$./wait_for_mysql.sh
Waiting for MySQL to accept connections...
Not ready yet — retrying in 3s...
Not ready yet — retrying in 3s...
Not ready yet — retrying in 3s...
✔ MySQL is ready
vriddh@prod-01:~/scripts$./wait_service.sh
Waiting... 0s / 60s
Waiting... 2s / 60s
✔ Service ready after 4s
BASH
# These two are exactly equivalent — pick the more readable one

# Option A: while (not ready)
while ! ping -c1 -W1 db-server >/dev/null 2>&1; do
  echo "  Waiting for db-server..."
  sleep 2
done

# Option B: until (ready)
until ping -c1 -W1 db-server >/dev/null 2>&1; do
  echo "  Waiting for db-server..."
  sleep 2
done
# Option B is cleaner — no negation ! needed

# ── Practical rule ────────────────────────────────────────
# Use until when your success condition is a simple command
# Use while when your condition uses comparisons like (( n < max ))

# until for deploy readiness check
until [[ $(kubectl get deployment myapp \
  -o jsonpath='{.status.readyReplicas}' 2>/dev/null) -ge 3 ]]; do
  echo "  Waiting for 3 replicas to be ready..."
  sleep 5
done
echo "  ✔ Deployment ready"

select automatically prints a numbered list of choices, reads the user's selection, and loops until break is called. It sets $REPLY to the raw input and the loop variable to the chosen item.

BASH
#!/usr/bin/env bash
# Simple select menu

PS3="Choose an option: "   # PS3 is the select prompt

select choice in "Start service" "Stop service" "Check status" "Quit"; do
  case "${choice}" in
    "Start service")
      echo "  Starting myapp..."
      systemctl start myapp
      ;;
    "Stop service")
      echo "  Stopping myapp..."
      systemctl stop myapp
      ;;
    "Check status")
      systemctl status myapp --no-pager
      ;;
    "Quit")
      echo "  Bye!"
      break
      ;;
    *)
      echo "  Invalid option: ${REPLY}"
      ;;
  esac
done
bash — select menu demo
vriddh@prod-01:~/scripts$./service_menu.sh
1) Start service
2) Stop service
3) Check status
4) Quit
Choose an option: 1
Starting myapp...
Choose an option: 3
● myapp.service - My Application
Active: active (running) since Fri 2026-05-01
Choose an option: 4
Bye!
BASH
#!/usr/bin/env bash
# Dynamic database selection menu

PS3="Select database to backup: "

# Build list dynamically from MySQL
databases=()
while IFS= read -r db; do
  [[ "${db}" =~ ^(information_schema|performance_schema|mysql|sys)$ ]] \
    && continue
  databases+=("${db}")
done < <(mysql -Nse "SHOW DATABASES" 2>/dev/null)

databases+=("ALL" "Cancel")

select db in "${databases[@]}"; do
  case "${db}" in
    "Cancel")
      echo "  Cancelled."
      break
      ;;
    "ALL")
      echo "  Backing up all databases..."
      mysqldump --all-databases > "/backups/all_$(date +%Y%m%d).sql"
      echo "  ✔ Done"
      break
      ;;
    "")
      echo "  Invalid choice: ${REPLY}"
      ;;
    *)
      echo "  Backing up: ${db}..."
      mysqldump "${db}" > "/backups/${db}_$(date +%Y%m%d).sql"
      echo "  ✔ Backup complete"
      break
      ;;
  esac
done
BASH
#!/usr/bin/env bash
# admin_menu.sh — Production server admin interface

set -euo pipefail

show_header() {
  clear
  echo "╔══════════════════════════════════╗"
  echo "║   VRIDDH SERVER ADMIN v1.0       ║"
  echo "║   Host: $(hostname -s)           ║"
  echo "║   User: ${USER}                  ║"
  echo "╚══════════════════════════════════╝"
  echo
}

disk_usage() {
  echo "── Disk Usage ──────────────────────"
  df -h --output=source,size,used,avail,pcent,target \
     | grep -v tmpfs | head -10
}

top_procs() {
  echo "── Top Processes ───────────────────"
  ps aux --sort=-%cpu | head -8
}

mem_usage() {
  echo "── Memory Usage ────────────────────"
  free -h
}

PS3=$'\nAdmin action: '

while true; do
  show_header
  select opt in \
    "Disk usage" \
    "Top processes" \
    "Memory usage" \
    "Restart nginx" \
    "Tail app log" \
    "Exit"; do
    case "${opt}" in
      "Disk usage")     disk_usage;   break ;;
      "Top processes")  top_procs;    break ;;
      "Memory usage")   mem_usage;    break ;;
      "Restart nginx")
        echo "Restarting nginx..."
        systemctl restart nginx && echo "✔ Done"
        break ;;
      "Tail app log")
        tail -f /var/log/myapp/app.log
        break ;;
      "Exit")
        echo "Goodbye."
        exit 0 ;;
      *) echo "Invalid: ${REPLY}" ;;
    esac
  done
  echo $'\nPress Enter to return to menu...'
  read -r
done
bash — admin_menu.sh
vriddh@prod-01:~/scripts$./admin_menu.sh
╔══════════════════════════════════╗
║ VRIDDH SERVER ADMIN v1.0 ║
║ Host: prod-01 ║
╚══════════════════════════════════╝
1) Disk usage 4) Restart nginx
2) Top processes 5) Tail app log
3) Memory usage 6) Exit
Admin action: 1
── Disk Usage ──────────────────────
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 18G 30G 38% /
/dev/sdb1 200G 172G 18G 91% /data
✔ until vs while vs select — Use until cmd when waiting for a command to succeed (no negation needed). Use while ! cmd when you prefer while-style. Use select for any interactive numbered menu — it handles the display, prompt, and input reading automatically. Always set PS3 before using select to give users a clear prompt.