Build Reliable Shell Prompts with whiptail

A script that just dumps text at the user is asking for trouble; whiptail gives you proper yes/no boxes, menus and checklists instead. The examples follow the installed newt 0.52.24 binary from package whiptail 0.52.24-2ubuntu2. The local manual identifies the documented interface as Whiptail Version 0.52.5, so treat the installed command as the final authority if another release behaves differently.

Allow about fifteen minutes. You need a terminal, a shell, and the whiptail package. These examples are ordinary user commands. They do not need sudo, and none changes persistent configuration.

1. Check the installed command

Confirm which executable your script will run and inspect its built-in option list:

$ command -v whiptail
/usr/bin/whiptail
$ whiptail --version
whiptail (newt): 0.52.24
$ whiptail --help
Box options:
        --msgbox <text> <height> <width>
        --yesno  <text> <height> <width>
        --menu <text> <height> <width> <listheight> [tag item] ...

The help output is shorter than the manual and shows the box families available on this installation. The same command can display messages, ask yes-or-no questions, accept text or passwords, present menus, manage checklists and radiolists, and show a gauge.

Checkpoint: if command -v prints nothing, stop and install the package through your normal distribution process. Do not silently replace the command with a different dialog implementation: option details and output conventions may differ.

2. Ask for confirmation and preserve the status

A yes/no box reports its answer purely through exit status, never through text you have to parse:

if whiptail --title "Apply changes" --yesno "Apply the prepared changes now?" 8 50; then
    printf '%s\n' 'User chose Yes'
else
    status=$?
    [ "$status" -eq 1 ] && printf '%s\n' 'User chose No or Cancel'
    [ "$status" -eq 255 ] && exit 255
fi

The dialog may adjust a height, width or list height of zero to fit the information, but explicit dimensions make a script easier to review.

Checkpoint: press No, then check that the script takes its non-destructive path. If pressing Escape matters, test it separately because it is not the same result as No.

3. Capture a menu choice without swallowing diagnostics

Whiptail normally writes the chosen input to standard error. That sounds backwards, but it lets the dialog use standard output for the terminal display. Capture the menu result with the standard file-descriptor shuffle:

choice=$(whiptail --title "Environment" \
    --menu "Where should the command run?" 10 50 3 \
    dev "Development" \
    stage "Staging" \
    prod "Production" \
    3>&1 1>&2 2>&3)
status=$?

if [ "$status" -ne 0 ]; then
    printf 'No environment selected, status %s\n' "$status" >&2
    exit "$status"
fi
printf 'selected environment: %s\n' "$choice"

The selected tag, such as stage, is captured. The item description is for the person at the terminal. The redirections keep the dialog's terminal rendering out of choice; the exact order matters, so keep it together rather than simplifying it from memory.

Tip: use --default-item stage when a menu has a sensible starting choice. Do not use a production default merely because it is convenient: the default is a cursor position, not confirmation.

4. Collect several values with a checklist

A checklist takes repeated triples of tag, item and initial status. The status is usually on or off. Space toggles an entry, and the selected tags are printed when the user accepts:

selected=$(whiptail --title "Optional components" \
    --checklist "Select components to enable" 12 60 4 \
    logs "Keep application logs" on \
    metrics "Expose metrics" off \
    backup "Create local backups" off \
    alerts "Send alerts" on \
    3>&1 1>&2 2>&3)
status=$?

if [ "$status" -ne 0 ]; then
    printf '%s\n' 'Checklist cancelled; no components changed' >&2
    exit "$status"
fi
printf 'selected tags: %s\n' "$selected"

Without --separate-output, the selected tags come out as one quoted result. Add that option when a simple line-oriented loop is more useful:

selected=$(whiptail --separate-output --checklist \
    "Select components" 10 50 3 \
    logs "Application logs" on \
    metrics "Metrics" off \
    backup "Backups" off \
    3>&1 1>&2 2>&3)
while IFS= read -r tag; do
    [ -n "$tag" ] && printf 'will enable: %s\n' "$tag"
done <<EOF
$selected
EOF

Do not execute state-changing work until the dialog has returned status 0. Cancellation is a normal control path, not a reason to assume that no output was produced.

5. Handle text and passwords carefully

An input box prints the entered string to standard error. The same capture pattern works:

name=$(whiptail --inputbox "Service name" 8 50 "example-service" 3>&1 1>&2 2>&3)
status=$?
[ "$status" -eq 0 ] || exit "$status"
printf 'name: %s\n' "$name"

A password box hides characters on screen, but it does not make careless handling safe. Never pass a password as the optional init argument: command arguments can be visible in the process table.

Avoid printing the captured value, logging it, placing it in a command line, or storing it in a file without a deliberate secret-management design. Shell variables also have lifetime and debugging risks. Prefer an application that reads a secret from a protected input channel, and clear temporary variables when your workflow permits it.

Security boundary: --passwordbox masks display only. It does not encrypt the value or protect a script that later echoes it.

6. Show progress with a gauge

A gauge reads an integer percentage from standard input, one value per line, and exits at end of file:

printf '%s\n' 0 25 50 75 100 | \
    whiptail --gauge "Preparing files" 7 60 0

This is useful for a known sequence of stages. The command itself does not perform the work: run the worker separately and feed progress from a controlled source. If the work can fail, capture the worker's status and do not report 100 merely because the gauge reached its final value.

The gauge also supports a special marker protocol for changing its prompt while it runs. Use it only when you have tested the producer and consumer together; a malformed stream can make progress confusing rather than informative.

7. Keep the display and the terminal predictable

--title labels the box and --backtitle labels the backdrop. --nocancel removes the Cancel button, so use it only when cancellation is genuinely unsafe or meaningless. --defaultno puts the cursor over No, which is a useful guard for destructive operations, though it does not stop a user selecting Yes.

--clear clears the screen to the screen attribute on exit, but the manual says it does not work in an xterm and descendants when alternate-screen switching is enabled. Do not make a script depend on a clean screen as evidence that an operation completed. --topleft, --scrolltext and --fb affect presentation, not the business decision.

If text or a menu item begins with a dash, pass -- before the box options' remaining arguments where the command syntax permits it. Whiptail otherwise interprets arguments beginning with a dash as options. Quote values supplied by users, and do not build an option string from untrusted text.

Done means