How do I prompt for Yes/No/Cancel input in a Linux shell script?

I want to pause input in a shell script, and prompt the user for choices.
The standard Yes, No, or Cancel type question.
How do I accomplish this in a typical bash prompt?

3

36 Answers

12

The simplest and most widely available method to get user input at a shell prompt is the read command. The best way to illustrate its use is a simple demonstration:

while true; do read -p "Do you wish to install this program? " yn case $yn in [Yy]* ) make install; break;; [Nn]* ) exit;; * ) echo "Please answer yes or no.";; esac
done

Another method, pointed out by Steven Huwig, is Bash's select command. Here is the same example using select:

echo "Do you wish to install this program?"
select yn in "Yes" "No"; do case $yn in Yes ) make install; break;; No ) exit;; esac
done

With select you don't need to sanitize the input – it displays the available choices, and you type a number corresponding to your choice. It also loops automatically, so there's no need for a while true loop to retry if they give invalid input.

Also, Léa Gris demonstrated a way to make the request language agnostic in her answer. Adapting my first example to better serve multiple languages might look like this:

set -- $(locale LC_MESSAGES)
yesexpr="$1"; noexpr="$2"; yesword="$3"; noword="$4"
while true; do read -p "Install (${yesword} / ${noword})? " yn if [[ "$yn" =~ $yesexpr ]]; then make install; exit; fi if [[ "$yn" =~ $noexpr ]]; then exit; fi echo "Answer ${yesword} / ${noword}."
done

Obviously other communication strings remain untranslated here (Install, Answer) which would need to be addressed in a more fully completed translation, but even a partial translation would be helpful in many cases.

Finally, please check out the excellent answer by F. Hauri.

11

At least five answers for one generic question.

Depending on

  • compliant: could work on poor systems with generic environments
  • specific: using so called bashisms

and if you want

  • simple ``in line'' question / answer (generic solutions)
  • pretty formatted interfaces, like or more graphical using libgtk or libqt...
  • use powerful readline history capability

1. POSIX generic solutions

You could use the read command, followed by if ... then ... else:

printf 'Is this a good question (y/n)? '
read answer
# if echo "$answer" | grep -iq "^y" ;then
if [ "$answer" != "${answer#[Yy]}" ] ;then # this grammar (the #[] operator) means that the variable $answer where any Y or y in 1st position will be dropped if they exist. echo Yes
else echo No
fi

(Thanks to Adam Katz's comment: Replaced the test above with one that is more portable and avoids one fork:)

POSIX, but single key feature

But if you don't want the user to have to hit Return, you could write:

(Edited: As @JonathanLeffler rightly suggest, saving stty's configuration could be better than simply force them to sane.)

printf 'Is this a good question (y/n)? '
old_stty_cfg=$(stty -g)
stty raw -echo ; answer=$(head -c 1) ; stty $old_stty_cfg # Careful playing with stty
if echo "$answer" | grep -iq "^y" ;then echo Yes
else echo No
fi

Note: This was tested under , , , and !

Same, but waiting explicitly for y or n:

#/bin/sh
printf 'Is this a good question (y/n)? '
old_stty_cfg=$(stty -g)
stty raw -echo
answer=$( while ! head -c 1 | grep -i '[ny]' ;do true ;done )
stty $old_stty_cfg
if echo "$answer" | grep -iq "^y" ;then echo Yes
else echo No
fi

Using dedicated tools

There are many tools which were built using libncurses, libgtk, libqt or other graphical libraries. For example, using whiptail:

if whiptail --yesno "Is this a good question" 20 60 ;then echo Yes
else echo No
fi

Depending on your system, you may need to replace whiptail with another similiar tool:

dialog --yesno "Is this a good question" 20 60 && echo Yes
gdialog --yesno "Is this a good question" 20 60 && echo Yes
kdialog --yesno "Is this a good question" 20 60 && echo Yes

where 20 is height of dialog box in number of lines and 60 is width of the dialog box. These tools all have near same syntax.

DIALOG=whiptail
if [ -x /usr/bin/gdialog ] ;then DIALOG=gdialog ; fi
if [ -x /usr/bin/xdialog ] ;then DIALOG=xdialog ; fi
...
$DIALOG --yesno ...

2. Bash specific solutions

Basic in line method

read -p "Is this a good question (y/n)? " answer
case ${answer:0:1} in y|Y ) echo Yes ;; * ) echo No ;;
esac

I prefer to use case so I could even test for yes | ja | si | oui if needed...

in line with single key feature

Under bash, we can specify the length of intended input for for the read command:

read -n 1 -p "Is this a good question (y/n)? " answer

Under bash, read command accepts a timeout parameter, which could be useful.

read -t 3 -n 1 -p "Is this a good question (y/n)? " answer
[ -z "$answer" ] && answer="Yes" # if 'yes' have to be default choice

3. Some tricks for dedicated tools

More sophisticated dialog boxes, beyond simple yes - no purposes:

dialog --menu "Is this a good question" 20 60 12 y Yes n No m Maybe

Progress bar:

dialog --gauge "Filling the tank" 20 60 0 < <( for i in {1..100};do printf "XXX\n%d\n%(%a %b %T)T progress: %d\nXXX\n" $i -1 $i sleep .033 done
) 

Little demo:

#!/bin/sh
while true ;do [ -x "$(which ${DIALOG%% *})" ] || DIALOG=dialog DIALOG=$($DIALOG --menu "Which tool for next run?" 20 60 12 2>&1 \ whiptail "dialog boxes from shell scripts" >/dev/tty \ dialog "dialog boxes from shell with ncurses" \ gdialog "dialog boxes from shell with Gtk" \ kdialog "dialog boxes from shell with Kde" ) || exit clear;echo "Choosed: $DIALOG." for i in `seq 1 100`;do date +"`printf "XXX\n%d\n%%a %%b %%T progress: %d\nXXX\n" $i $i`" sleep .0125 done | $DIALOG --gauge "Filling the tank" 20 60 0 $DIALOG --infobox "This is a simple info box\n\nNo action required" 20 60 sleep 3 if $DIALOG --yesno "Do you like this demo?" 20 60 ;then AnsYesNo=Yes; else AnsYesNo=No; fi AnsInput=$($DIALOG --inputbox "A text:" 20 60 "Text here..." 2>&1 >/dev/tty) AnsPass=$($DIALOG --passwordbox "A secret:" 20 60 "First..." 2>&1 >/dev/tty) $DIALOG --textbox /etc/motd 20 60 AnsCkLst=$($DIALOG --checklist "Check some..." 20 60 12 \ Correct "This demo is useful" off \ Fun "This demo is nice" off \ Strong "This demo is complex" on 2>&1 >/dev/tty) AnsRadio=$($DIALOG --radiolist "I will:" 20 60 12 \ " -1" "Downgrade this answer" off \ " 0" "Not do anything" on \ " +1" "Upgrade this anser" off 2>&1 >/dev/tty) out="Your answers:\nLike: $AnsYesNo\nInput: $AnsInput\nSecret: $AnsPass" $DIALOG --msgbox "$out\nAttribs: $AnsCkLst\nNote: $AnsRadio" 20 60 done

More samples? Have a look at Using whiptail for choosing USB device and USB removable storage selector: USBKeyChooser

5. Using readline's history

Example:

#!/bin/bash
set -i
HISTFILE=~/.myscript.history
history -c
history -r
myread() { read -e -p '> ' $1 history -s ${!1}
}
trap 'history -a;exit' 0 1 2 3 6
while myread line;do case ${line%% *} in exit ) break ;; * ) echo "Doing something with '$line'" ;; esac done

This will create a file .myscript.history in your $HOME directory, than you could use readline's history commands, like Up, Down, Ctrl+r and others.

8
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
3

You can use the built-in read command ; Use the -p option to prompt the user with a question.

Since BASH4, you can now use -i to suggest an answer :

read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
echo $FILEPATH

(But remember to use the "readline" option -e to allow line editing with arrow keys)

If you want a "yes / no" logic, you can do something like this:

read -e -p "
List the content of your home dir ? [Y/n] " YN
[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/
5

Bash has select for this purpose.

select result in Yes No Cancel
do echo $result
done
4
read -p "Are you alright? (y/n) " RESP
if [ "$RESP" = "y" ]; then echo "Glad to hear it"
else echo "You need more bash programming"
fi
0
inquire () { echo -n "$1 [y/n]? " read answer finish="-1" while [ "$finish" = '-1' ] do finish="1" if [ "$answer" = '' ]; then answer="" else case $answer in y | Y | yes | YES ) answer="y";; n | N | no | NO ) answer="n";; *) finish="-1"; echo -n 'Invalid response -- please reenter:'; read answer;; esac fi done
}
... other stuff
inquire "Install now?"
...
4

Here's something I put together:

#!/bin/sh
promptyn () { while true; do read -p "$1 " yn case $yn in [Yy]* ) return 0;; [Nn]* ) return 1;; * ) echo "Please answer yes or no.";; esac done
}
if promptyn "is the sky blue?"; then echo "yes"
else echo "no"
fi

I'm a beginner, so take this with a grain of salt, but it seems to work.

5

You want:

  • Bash builtin commands (i.e. portable)
  • Check TTY
  • Default answer
  • Timeout
  • Colored question

Snippet

do_xxxx=y # In batch mode => Default is Yes
[[ -t 0 ]] && # If TTY => Prompt the question
read -n 1 -p $'\e[1;32m
Do xxxx? (Y/n)\e[0m ' do_xxxx # Store the answer in $do_xxxx
if [[ $do_xxxx =~ ^(y|Y|)$ ]] # Do if 'y' or 'Y' or empty
then xxxx
fi

Explanations

  • [[ -t 0 ]] && read ... => Call command read if TTY
  • read -n 1 => Wait for one character
  • $'\e[1;32m ... \e[0m ' => Print in green
    (green is fine because readable on both white/black backgrounds)
  • [[ $do_xxxx =~ ^(y|Y|)$ ]] => bash regex

Timeout => Default answer is No

do_xxxx=y
[[ -t 0 ]] && { # Timeout 5 seconds (read -t 5)
read -t 5 -n 1 -p $'\e[1;32m
Do xxxx? (Y/n)\e[0m ' do_xxxx || # read 'fails' on timeout
do_xxxx=n ; } # Timeout => answer No
if [[ $do_xxxx =~ ^(y|Y|)$ ]]
then xxxx
fi
0

The easiest way to achieve this with the least number of lines is as follows:

read -p "<Your Friendly Message here> : y/n/cancel" CONDITION;
if [ "$CONDITION" == "y" ]; then # do something here!
fi

The if is just an example: it is up to you how to handle this variable.

Use the read command:

echo Would you like to install? "(Y or N)"
read x
# now check if $x is "y"
if [ "$x" = "y" ]; then # do something here!
fi

and then all of the other stuff you need

0

This solution reads a single character and calls a function on a yes response.

read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then do_something
fi
1

It is possible to handle a locale-aware "Yes / No choice" in a POSIX shell; by using the entries of the LC_MESSAGES locale category, witch provides ready-made RegEx patterns to match an input, and strings for localized Yes No.

#!/usr/bin/env sh
# Getting LC_MESSAGES values into variables
# shellcheck disable=SC2046 # Intended IFS splitting
IFS='
' set -- $(locale LC_MESSAGES)
yesexpr="$1"
noexpr="$2"
yesstr="$3"
nostr="$4"
messages_codeset="$5" # unused here, but kept as documentation
# Display Yes / No ? prompt into locale
echo "$yesstr / $nostr ?"
# Read answer
read -r yn
# Test answer
case "$yn" in
# match only work with the character class from the expression ${yesexpr##^}) echo "answer $yesstr" ;; ${noexpr##^}) echo "answer $nostr" ;;
esac

EDIT: As @Urhixidur mentioned in his comment:

Unfortunately, POSIX only specifies the first two (yesexpr and noexpr). On Ubuntu 16, yesstr and nostr are empty.

See:

LC_MESSAGES

The yesstr and nostr locale keywords and the YESSTR and NOSTR langinfo items were formerly used to match user affirmative and negative responses. In POSIX.1-2008, the yesexpr, noexpr, YESEXPR, and NOEXPR extended regular expressions have replaced them. Applications should use the general locale-based messaging facilities to issue prompting messages which include sample desired responses.

Alternatively using locales the Bash way:

#!/usr/bin/env bash
IFS=$'\n' read -r -d '' yesexpr noexpr _ < <(locale LC_MESSAGES)
printf -v yes_or_no_regex "(%s)|(%s)" "$yesexpr" "$noexpr"
printf -v prompt $"Please answer Yes (%s) or No (%s): " "$yesexpr" "$noexpr"
declare -- answer=;
until [[ "$answer" =~ $yes_or_no_regex ]]; do read -rp "$prompt" answer
done
if [[ -n "${BASH_REMATCH[1]}" ]]; then echo $"You answered: Yes"
else echo $"No, was your answer."
fi

The answer is matched using locale environment's provided regexps.

To translate the remaining messages, use bash --dump-po-strings scriptname to output the po strings for localization:

#: scriptname:8
msgid "Please answer Yes (%s) or No (%s): "
msgstr ""
#: scriptname:17
msgid "You answered: Yes"
msgstr ""
#: scriptname:19
msgid "No, was your answer."
msgstr ""
4

To get a nice ncurses-like inputbox use the command dialog like this:

#!/bin/bash
if (dialog --title "Message" --yesno "Want to do something risky?" 6 25)
# message box will have the size 25x6 characters
then echo "Let's do something risky" # do something risky
else echo "Let's stay boring"
fi

The dialog package is installed by default at least with SUSE Linux. Looks like:the "dialog" command in action

1

In my case I needed to read from a downloaded script i.e.,

curl -Ss | sh

The line read -r yn </dev/tty allowed it to read input in this case.

printf "These files will be uploaded. Is this ok? (y/N) "
read -r yn </dev/tty
if [ "$yn" = "y" ]; then # Yes
else # No
fi
1

Single keypress only

Here's a longer, but reusable and modular approach:

  • Returns 0=yes and 1=no
  • No pressing enter required - just a single character
  • Can press enter to accept the default choice
  • Can disable default choice to force a selection
  • Works for both zsh and bash.

Defaulting to "no" when pressing enter

Note that the N is capitalsed. Here enter is pressed, accepting the default:

$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]?

Also note, that [y/N]? was automatically appended. The default "no" is accepted, so nothing is echoed.

Re-prompt until a valid response is given:

$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]? X
Show dangerous command [y/N]? y
rm *

Defaulting to "yes" when pressing enter

Note that the Y is capitalised:

$ confirm_yes "Show dangerous command" && echo "rm *"
Show dangerous command [Y/n]?
rm *

Above, I just pressed enter, so the command ran.

No default on enter - require y or n

$ get_yes_keypress "Here you cannot press enter. Do you like this [y/n]? "
Here you cannot press enter. Do you like this [y/n]? k
Here you cannot press enter. Do you like this [y/n]?
Here you cannot press enter. Do you like this [y/n]? n
$ echo $?
1

Here, 1 or false was returned. Note that with this lower-level function you'll need to provide your own [y/n]? prompt.

Code

# Read a single char from /dev/tty, prompting with "$*"
# Note: pressing enter will return a null string. Perhaps a version terminated with X and then remove it in caller?
# See for dealing with multi-byte, etc.
function get_keypress { local REPLY IFS= >/dev/tty printf '%s' "$*" [[ $ZSH_VERSION ]] && read -rk1 # Use -u0 to read from STDIN # See regarding '\n' -> '' [[ $BASH_VERSION ]] && </dev/tty read -rn1 printf '%s' "$REPLY"
}
# Get a y/n from the user, return yes=0, no=1 enter=$2
# Prompt using $1.
# If set, return $2 on pressing enter, useful for cancel or defualting
function get_yes_keypress { local prompt="${1:-Are you sure [y/n]? }" local enter_return=$2 local REPLY # [[ ! $prompt ]] && prompt="[y/n]? " while REPLY=$(get_keypress "$prompt"); do [[ $REPLY ]] && printf '\n' # $REPLY blank if user presses enter case "$REPLY" in Y|y) return 0;; N|n) return 1;; '') [[ $enter_return ]] && return "$enter_return" esac done
}
# Credit:
# Prompt to confirm, defaulting to NO on <enter>
# Usage: confirm "Dangerous. Are you sure?" && rm *
function confirm { local prompt="${*:-Are you sure} [y/N]? " get_yes_keypress "$prompt" 1
}
# Prompt to confirm, defaulting to YES on <enter>
function confirm_yes { local prompt="${*:-Are you sure} [Y/n]? " get_yes_keypress "$prompt" 0
}
2

You can use the default REPLY on a read, convert to lowercase and compare to a set of variables with an expression.
The script also supports ja/si/oui

read -rp "Do you want a demo? [y/n/c] "
[[ ${REPLY,,} =~ ^(c|cancel)$ ]] && { echo "Selected Cancel"; exit 1; }
if [[ ${REPLY,,} =~ ^(y|yes|j|ja|s|si|o|oui)$ ]]; then echo "Positive"
fi
read -e -p "Enter your choice: " choice

The -e option enables the user to edit the input using arrow keys.

If you want to use a suggestion as input:

read -e -i "yes" -p "Enter your choice: " choice

-i option prints a suggestive input.

1

I noticed that no one posted an answer showing multi-line echo menu for such simple user input so here is my go at it:

#!/bin/bash
function ask_user() {
echo -e "
#~~~~~~~~~~~~#
| 1.) Yes |
| 2.) No |
| 3.) Quit |
#~~~~~~~~~~~~#\n"
read -e -p "Select 1: " choice
if [ "$choice" == "1" ]; then do_something
elif [ "$choice" == "2" ]; then do_something_else
elif [ "$choice" == "3" ]; then clear && exit 0
else echo "Please select 1, 2, or 3." && sleep 3 clear && ask_user
fi
}
ask_user

This method was posted in the hopes that someone may find it useful and time-saving.

One-liner:

read -p "Continue? [Enter] → Yes, [Ctrl]+[C] → No."

This assumes that "No" and "Cancel" have the same outcome, so no reason to treat them differently.

Check this

read -p "Continue? (y/n): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1

Multiple choice version:

ask () { # $1=question $2=options # set REPLY # options: x=..|y=.. while $(true); do printf '%s [%s] ' "$1" "$2" stty cbreak REPLY=$(dd if=/dev/tty bs=1 count=1 2> /dev/null) stty -cbreak test "$REPLY" != "$(printf '\n')" && printf '\n' ( IFS='|' for o in $2; do if [ "$REPLY" = "${o%%=*}" ]; then printf '\n' break fi done ) | grep ^ > /dev/null && return done
}

Example:

$ ask 'continue?' 'y=yes|n=no|m=maybe'
continue? [y=yes|n=no|m=maybe] g
continue? [y=yes|n=no|m=maybe] k
continue? [y=yes|n=no|m=maybe] y
$

It will set REPLY to y (inside the script).

Inspired by the answers of @Mark and @Myrddin I created this function for a universal prompt

uniprompt(){ while true; do echo -e "$1\c" read opt array=($2) case "${array[@]}" in *"$opt"*) eval "$3=$opt";return 0;; esac echo -e "$opt is not a correct value\n" done
}

use it like this:

unipromtp "Select an option: (a)-Do one (x)->Do two (f)->Do three : " "a x f" selection
echo "$selection"
0

I suggest you use dialog...

Linux Apprentice: Improve Bash Shell Scripts Using Dialog

The dialog command enables the use of window boxes in shell scripts to make their use more interactive.

it's simple and easy to use, there's also a gnome version called gdialog that takes the exact same parameters, but shows it GUI style on X.

1

more generic would be:

function menu(){ title="Question time" prompt="Select:" options=("Yes" "No" "Maybe") echo "$title" PS3="$prompt" select opt in "${options[@]}" "Quit/Cancel"; do case "$REPLY" in 1 ) echo "You picked $opt which is option $REPLY";; 2 ) echo "You picked $opt which is option $REPLY";; 3 ) echo "You picked $opt which is option $REPLY";; $(( ${#options[@]}+1 )) ) clear; echo "Goodbye!"; exit;; *) echo "Invalid option. Try another one.";continue;; esac done return
}
yn() { if [[ 'y' == `read -s -n 1 -p "[y/n]: " Y; echo $Y` ]]; then eval $1; else eval $2; fi }
yn 'echo yes' 'echo no'
yn 'echo absent no function works too!'
1

One simple way to do this is with xargs -p or gnu parallel --interactive.

I like the behavior of xargs a little better for this because it executes each command immediately after the prompt like other interactive unix commands, rather than collecting the yesses to run at the end. (You can Ctrl-C after you get through the ones you wanted.)

e.g.,

echo *.xml | xargs -p -n 1 -J {} mv {} backup/
2

As a friend of a one line command I used the following:

while [ -z $prompt ]; do read -p "Continue (y/n)?" choice;case "$choice" in y|Y ) prompt=true; break;; n|N ) exit 0;; esac; done; prompt=;

Written longform, it works like this:

while [ -z $prompt ]; do read -p "Continue (y/n)?" choice; case "$choice" in y|Y ) prompt=true; break;; n|N ) exit 0;; esac;
done;
prompt=;
2

I've used the case statement a couple of times in such a scenario, using the case statment is a good way to go about it. A while loop, that ecapsulates the case block, that utilizes a boolean condition can be implemented in order to hold even more control of the program, and fulfill many other requirements. After the all the conditions have been met, a break can be used which will pass control back to the main part of the program. Also, to meet other conditions, of course conditional statements can be added to accompany the control structures: case statement and possible while loop.

Example of using a case statement to fulfill your request

#! /bin/sh
# For potential users of BSD, or other systems who do not
# have a bash binary located in /bin the script will be directed to
# a bourne-shell, e.g. /bin/sh
# NOTE: It would seem best for handling user entry errors or
# exceptions, to put the decision required by the input
# of the prompt in a case statement (case control structure),
echo Would you like us to perform the option: "(Y|N)"
read inPut
case $inPut in # echoing a command encapsulated by # backticks (``) executes the command "Y") echo `Do something crazy` ;; # depending on the scenario, execute the other option # or leave as default "N") echo `execute another option` ;;
esac
exit

Yes / No / Cancel

Function

#!/usr/bin/env bash
@confirm() { local message="$*" local result='' echo -n "> $message (Yes/No/Cancel) " >&2 while [ -z "$result" ] ; do read -s -n 1 choice case "$choice" in y|Y ) result='Y' ;; n|N ) result='N' ;; c|C ) result='C' ;; esac done echo $result
}

Usage

case $(@confirm 'Confirm?') in Y ) echo "Yes" ;; N ) echo "No" ;; C ) echo "Cancel" ;;
esac

Confirm with clean user input

Function

#!/usr/bin/env bash
@confirm() { local message="$*" local result=3 echo -n "> $message (y/n) " >&2 while [[ $result -gt 1 ]] ; do read -s -n 1 choice case "$choice" in y|Y ) result=0 ;; n|N ) result=1 ;; esac done return $result
}

Usage

if @confirm 'Confirm?' ; then echo "Yes"
else echo "No"
fi
0 12

You Might Also Like