ask – yes/no question prompt in Bash

This is a general-purpose script to ask Yes/No questions in Bash, either with or without a default answer. It keeps repeating the question until it gets a valid answer.

The script

Save this somewhere in PATH with the name ask, and make it executable (chmod +x ask).

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

prompt=${1:-}

# Display usage info if no prompt is given
if [[ -z $prompt ]]; then
    echo "Usage: $(basename "$0") <prompt> [default]" >&2
    exit 2
fi

# Determine the default value
if [[ ${2:-} = 'Y' || ${2:-} = 'y' ]]; then
    default='Y'
    yn='Y/n'
elif [[ ${2:-} = 'N' || ${2:-} = 'n' ]]; then
    default='N'
    yn='y/N'
else
    default=''
    yn='y/n'
fi

# Ask the question (not using "read -p" as it uses stderr not stdout)
echo -n "$prompt [$yn] "

# Repeat until we get a valid answer
result=

while [[ -z $result ]]; do

    # Read a single character, without echoing it
    read -rsn1 reply

    # If the user pressed enter, use the default value
    if [[ -z $reply ]]; then
        reply=$default
    fi

    # Check if the reply is valid
    case "$reply" in
        Y*|y*) result=0 ;;
        N*|n*) result=1 ;;
    esac

done

# Output the reply, or the default value, so the user can see it
echo "$reply"

# Exit with the relevant code for true or false
exit $result

Example usage

if ask "Do you want to do such-and-such?"; then
    echo "Yes"
else
    echo "No"
fi

Default to Yes if the user presses enter without giving an answer:

if ask "Do you want to do such-and-such?" Y; then
    echo "Yes"
else
    echo "No"
fi

Default to No if the user presses enter without giving an answer:

if ask "Do you want to do such-and-such?" N; then
    echo "Yes"
else
    echo "No"
fi

Only do something if you say Yes:

if ask "Do you want to do such-and-such?"; then
    said_yes
fi

Only do something if you say No:

if ! ask "Do you want to do such-and-such?"; then
    said_no
fi

Or if you prefer the shorter versions:

ask "Do you want to do such-and-such?" && said_yes

ask "Do you want to do such-and-such?" || said_no

Other notes

If input/output to your script may be redirected from somewhere else, but you still want to prompt the user interactively, you may need to redirect it again for this one call:

if ask "Something" </dev/tty >/dev/tty; then
    said_yes
fi

In a previous version of this article, I defined a function (ask() { ... }) rather than a script, because that's what I was used to doing in PHP - but I've since realised this is the more standard approach (the Unix philosophy). The usage is the same either way, but a function needs to use return rather than exit.