Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 8 min read

Building Bash TUI Menus with dialog and whiptail

Not every ops tool needs a web UI. A dialog-based menu turns a pile of bash scripts into something a tired teammate can run at 3am without memorizing flags.

  • #bash
  • #python
  • #dialog
  • #whiptail
  • #tui
  • #automation

Some of the most-used tools I’ve built were never web apps. They were bash scripts with a blue-and-grey text menu, run over SSH on a bastion host, that let an on-call engineer pick “restart the worker pool” or “rotate the cache” from a list instead of remembering an exact incantation. That’s what dialog and whiptail are for: turning raw scripts into a TUI that’s hard to fumble.

It’s an old technique — Debian’s installer is built on it — but it’s exactly right for internal ops tooling. No dependencies beyond a single package, runs anywhere there’s a terminal, and it makes destructive operations require a deliberate selection instead of a hastily-typed flag.

dialog vs whiptail

They’re near-drop-in compatible. whiptail ships by default on Debian/Ubuntu (it’s part of the installer tooling); dialog is more feature-rich and common on RHEL-family systems. Both speak the same basic widget vocabulary — menus, message boxes, input boxes, checklists, gauges. Write to the common subset and your script runs with either. I’ll use dialog in examples; swap the binary name for whiptail and most of it just works.

# pick whichever is installed
TUI=$(command -v dialog || command -v whiptail)

The key trick: output comes back on stderr

This trips up everyone the first time. dialog draws its UI on the screen (stdout) and returns your selection on stderr. So you redirect stderr to capture the choice, usually swapping the two streams:

choice=$(dialog --clear \
  --title "Ops Console" \
  --menu "Choose an action:" 15 50 4 \
    1 "Show service status" \
    2 "Restart worker pool" \
    3 "Rotate cache" \
    4 "Tail application logs" \
  2>&1 >/dev/tty)

That 2>&1 >/dev/tty is the magic: send dialog’s drawing to the real terminal, and route its stderr (your selection) into the $() capture. Forget it and you’ll capture the UI escape codes instead of the answer.

The exit code tells you whether they confirmed or cancelled:

if [[ $? -ne 0 ]]; then
  clear; echo "Cancelled."; exit 0
fi

A complete menu loop

Here’s the shape of a real tool — a menu that loops until the user quits, with each action in its own function:

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

show_status() {
  systemctl status myapp --no-pager > /tmp/out.$$ 2>&1 || true
  dialog --title "Status" --textbox /tmp/out.$$ 22 76
  rm -f /tmp/out.$$
}

restart_pool() {
  dialog --title "Confirm" --yesno \
    "Restart the worker pool? This drops in-flight jobs." 8 50 || return
  systemctl restart myapp-worker
  dialog --msgbox "Worker pool restarted." 6 40
}

while true; do
  choice=$(dialog --clear --title "Ops Console" \
    --menu "Choose an action:" 15 50 5 \
      1 "Show service status" \
      2 "Restart worker pool" \
      3 "Quit" \
    2>&1 >/dev/tty) || break

  case "$choice" in
    1) show_status ;;
    2) restart_pool ;;
    3) break ;;
  esac
done
clear

The --yesno guard before the restart is the whole point of building a TUI for ops: a destructive action becomes a deliberate two-step confirmation instead of one fat-fingered command. The --textbox widget is perfect for showing command output — it’s scrollable, so long status dumps are readable.

Widgets worth knowing

  • --inputbox — free text, returned on stderr like menus. For “enter the hostname.”
  • --passwordbox — same, but masks input. Don’t log what you capture.
  • --checklist / --radiolist — multi-select and single-select with on/off defaults. Great for “which hosts to act on.”
  • --gauge — a progress bar fed by piping percentages into it:
( for pct in 0 25 50 75 100; do echo $pct; sleep 0.4; done ) \
  | dialog --gauge "Deploying..." 8 50 0
  • --tailbox — live-follows a growing file, ideal for watching a log during an operation.

Keeping it maintainable

A few habits keep these tools from rotting:

  • One function per action. The menu just dispatches. Each action is independently testable by calling the function directly.
  • set -euo pipefail at the top, but be deliberate — dialog returning non-zero on Cancel is expected, so handle those exit codes with || return rather than letting the script abort.
  • Always clear on exit. Otherwise you leave the terminal in a half-drawn state.
  • Confirm before anything destructive, and make the confirmation message state the blast radius (“drops in-flight jobs”), not just “Are you sure?”.
  • Detect the binary rather than hardcoding dialog — the same script then runs on both Debian and RHEL boxes.

When to reach for something else

A dialog menu is perfect for a fixed set of operator actions on a single host. When you need rich layouts, live-updating panels, or real interactivity, that’s where Python TUIs (Textual, urwid) earn their keep. But for “give my teammates a safe, discoverable front-end to these ten scripts,” dialog is faster to build and has zero runtime you have to install or trust.

The goal isn’t a pretty interface. It’s making the safe path the easy path, so a half-asleep engineer picks the right action from a list instead of guessing at flags.

For more practical shell patterns, browse the Bash & Python automation guides, or grab a prompt to scaffold your own console.

TUI scripts still run real commands. Gate destructive actions behind explicit confirmations and test the cancel paths before handing the tool to your team.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.