How do I prompt for Yes/No/Cancel input in a Linux shell script?
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.";; esacdone
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;; esacdone
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)yesptrn="$1"; noptrn="$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.
At least five answers for one generic question.
Depending on
- posix compliant: could work on poor systems with generic shell environments
- bash specific: using so called bashisms
and if you want
- simple ``in line'' question / answer (generic solutions)
- pretty formatted interfaces, like ncurses 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
:
echo -n "Is this a good question (y/n)? "read answer
# if echo "$answer" | grep -iq "^y" ;then
if [ "$answer" != "${answer#[Yy]}" ] ;then echo Yeselse echo Nofi
(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.)
echo -n "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 sttyif echo "$answer" | grep -iq "^y" ;then echo Yeselse echo Nofi
Note: This was tested under sh, bash, ksh, dash and busybox!
Same, but waiting explicitly for y or n:
#/bin/shecho -n "Is this a good question (y/n)? "old_stty_cfg=$(stty -g)stty raw -echoanswer=$( while ! head -c 1 | grep -i '[ny]' ;do true ;done )stty $old_stty_cfgif echo "$answer" | grep -iq "^y" ;then echo Yeselse echo Nofi
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 Yeselse echo Nofi
Depending on your system, you may need to replace whiptail
with another similiar tool:
dialog --yesno "Is this a good question" 20 60 && echo Yesgdialog --yesno "Is this a good question" 20 60 && echo Yeskdialog --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=whiptailif [ -x /usr/bin/gdialog ] ;then DIALOG=gdialog ; fiif [ -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)? " answercase ${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/shwhile 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 sample? Have a look at Using whiptail for choosing USB device and USB removable storage selector: USBKeyChooser
5. Using readline's history
Example:
#!/bin/bashset -iHISTFILE=~/.myscript.historyhistory -chistory -rmyread() { read -e -p '> ' $1 history -s ${!1}}trap 'history -a;exit' 0 1 2 3 6while 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.