1889

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
  • 41
    Just as a note: convention for prompts are such that if you present a [yn] option, the one that is capitalized is default, i.e. [Yn] defaults to "yes", and [yN] defaults to "no". See ux.stackexchange.com/a/40445/43532
    – Tyzoid
    Commented May 30, 2019 at 19:46
  • 4
    Anyone coming here from ZSH, see this answer for how to use the read command to prompt
    – smac89
    Commented Dec 20, 2019 at 4:34
  • 2
    You can also consider my related Q/A on U&L.SE about the canonical way to pause in bash. The provided results could easily be transferred.
    – Cadoiz
    Commented Oct 15, 2021 at 8:57

39 Answers 39

2058

A widely available method to get user input at a shell prompt is the read command. Here is a 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
  • 41
    Using Bash in OS X Leopard, I changed exit to break to keep from closing the tab when I selected 'no'. Commented Dec 2, 2009 at 18:34
  • 1
    How does this work with options longer than Yes or No? In the case clause, do you write something like: Install program and do nothing afterwards ) make install; break;
    – Shawn
    Commented Feb 29, 2012 at 6:12
  • 1
    @Shawn Using read you can, of course, use any glob or regex pattern supported by the bash shell's switch statement. It just matches the text typed to your patterns until it finds a match. Using select, the list of choices is given to the command and it displays them to the user. You can have the items in the list be as long or as short as you like. I recommand checking their man pages for comprehensive usage information. Commented Feb 29, 2012 at 21:45
  • 4
    why is there a break in the select if there is no loop?
    – Jayen
    Commented Jun 16, 2014 at 10:46
  • 1
    FWIW, I used this example to create a script that I intended to trigger via a remote SSH session. My SSH command looked like this: ssh my-server 'path/to/myscript.sh'. When executing this way, the prompt text for the read -p command does not show up. However, output from the echo command does. So for me, the better solution was to use echo -n "Do something? " followed by read yn.
    – Travesty3
    Commented Jan 4, 2018 at 15:08
708

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:

#/bin/sh
printf 'Is this a good question (y/n)? '
read answer

if [ "$answer" != "${answer#[Yy]}" ] ;then 
    echo Yes
else
    echo No
fi

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

1.1 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.)

#/bin/sh
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 [ "$answer" != "${answer#[Yy]}" ];then
    echo Yes
else
    echo No
fi

1.2 POSIX, but single key feature localized

Using locale command, inspired by Léa Gris's idea for handling locales, but for this I just need *yes expression*:

#/bin/sh
yExpr=$(locale yesexpr) 
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 [ "$answer" != "${answer#${yExpr#^}}" ];then
    echo Yes
else
    echo No
fi

Note: This was tested under , , , and !

1.3 Same, but waiting explicitly for y, n or from locale

#/bin/sh
set -- $(locale LC_MESSAGES)
yExpr="$1"; nExpr="$2"
printf 'Is this a good question (y/n)? '
old_stty_cfg=$(stty -g)
stty raw -echo
answer=$( while ! head -c 1 | grep "$yExpr\|$nExpr" ;do true ;done )
stty $old_stty_cfg
if [ "$answer" != "${answer#${yExpr#^}}" ];then
    echo Yes
else
    echo No
fi

2. 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 ...

3. 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

Timeout with countdown:

i=6 ;while ((i-->1)) &&
! read -sn 1 -t 1 -p $'\rIs this a good question (Y/n)? '$i$'..\e[3D' answer;do
  :;done ;[[ $answer == [nN] ]] && answer=No || answer=Yes ;echo "$answer "

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" ) || break
    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.

11
  • 6
    Note that stty provides the -g option for use: old_stty=$(stty -g); stty raw -echo; …; stty "$old_stty". This restores the setting exactly as they were found, which may or may not be the same as stty -sane. Commented Feb 7, 2015 at 5:09
  • 7
    read answer will interpret backslashes before spaces and line feeds, and otherwise strip them which is rarely intended. Use read -r answer instead as per SC2162.
    – vlfig
    Commented Mar 31, 2017 at 14:03
  • 1
    The "Using readline's history" method is so terribly inappropriate for the OP's question. I'm glad you included it anyway. I have dozens of much more complicated scripts that I intend to update with that pattern! Commented Oct 19, 2017 at 4:18
  • 7
    You can use case for POSIX as well as bash (use a wildcard condition rather than a bash substring: case $answer in; [Yy]* ) echo Yes ;;), but I prefer using a conditional statement instead, favoring [ "$answer" != "${answer#[Yy]}" ] over your echo "$answer" | grep -iq ^y. It's more portable (some non-GNU greps don't implement -q correctly) and it doesn't have the system call. ${answer#[Yy]} uses parameter expansion to remove Y or y from the beginning of $answer, causing an inequality when either is present. This works in any POSIX shell (dash, ksh, bash, zsh, busybox, etc).
    – Adam Katz
    Commented Apr 10, 2018 at 16:44
  • 3
    @CarterPape Yes, this was a joke! But in this detalled answer, you may find a lot of tips (second point present at least 3 different methods)! And ... from approx 5 years now, you are first to tell about my counting method! ;-)) Commented Apr 1, 2020 at 20:09
359
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
3
  • 35
    I disagree, because it only implements a portion of the functionality of the 'Yes, No, Cancel' dialog in DOS. The part it fails to implement is input checking... looping until a valid answer is received. Commented Sep 23, 2016 at 15:49
  • 4
    (The original question title was "How do I prompt for input in a Linux shell script?")
    – Pistos
    Commented Jul 9, 2017 at 16:53
  • 8
    But the original question description is unchanged, and always asked for a response to a Yes/No/Cancel prompt. The title has been updated to be clearer than my original one, but the question description was always clear (in my opinion). Commented Sep 6, 2017 at 13:52
179

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
  • 6
    It should be noted that FILEPATH is the variable name you have chosen, and is set with the answer to the command prompt. So if you were to then run vlc "$FILEPATH", for example, vlc would open that file.
    – Ken Sharp
    Commented Feb 23, 2015 at 1:45
  • What's the benefit of -e in the second example (simple yes/no)?
    – JBallin
    Commented Nov 11, 2018 at 20:39
  • Any reason to use -e -p instead of -ep?
    – JBallin
    Commented Nov 11, 2018 at 20:42
  • 1
    Without the -e flag/option, you might (depending on the implementation) not be able to type "y", and then change your mind and replace it with a "n" (or anything else for that matter) ; When documenting a command, listing the options separately is better for readability/clarity, among other reasons.
    – yPhil
    Commented Nov 13, 2018 at 11:16
  • Just a hint: pay attention to really use bash, not sh like described in this answer on U&L.SE.
    – Cadoiz
    Commented Oct 15, 2021 at 9:46
128

Bash has select for this purpose. Here's how you would use it in a script:

select result in Yes No Cancel
do
    echo $result
done

This is what it would look like to use:

$ bash examplescript.sh
1) Yes
2) No
3) Cancel
#? 1
Yes
#? 2
No
#? 3
Cancel
#?
4
  • 24
    +1 Geniously simple solution. Only thing: This will prompt and promt and prompt... until you add an exit inside :)
    – kaiser
    Commented Feb 28, 2013 at 0:24
  • 7
    (kaiser: To break from it, just enter the EOT: Ctrl-D. But of course, real code using it will need a break or an exit in the body.)
    – Zorawar
    Commented Apr 22, 2014 at 22:50
  • 19
    This will not allow you to enter y or n, though.You choose by entering 1 2 or 3.
    – djjeck
    Commented Sep 10, 2014 at 16:03
  • 8
    exit will exit the script all together, break will only exit the loop you are in (if you are on a while or case loop)
    – wranvaud
    Commented Nov 6, 2016 at 17:48
66
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
1
47
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
  • 2
    Put four spaces in the beginning of each line to preserve the formatting of code. Commented Oct 22, 2008 at 17:15
  • 10
    Why we providing 'y' and 'n' as parameters to inquire() if the case switches are hardcoded? That's just asking for misuse. They are fixed parameters, not changable, so the echo on line 2 should read: echo -n "$1 [Y/N]? " They can't be changed, so they shouldn't be supplied. Commented Oct 22, 2008 at 20:06
  • 1
    @MyrddinEmrys Could you please elaborate your comment? Or post a link to an article or a couple of keywords so I could do research on my own. Commented Mar 26, 2016 at 20:46
  • 4
    @MateuszPiotrowski The answer has been edited and improved since I made my comment. You can click the 'edited Dec 23' link above to view all the past versions of this answer. Back in 2008, the code was quite different. Commented Mar 27, 2016 at 5:21
45

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
  • 9
    If you change case $yn in to case ${yn:-$2} in then you can use the second argument as the default value, Y or N.
    – jchook
    Commented Jul 24, 2013 at 20:21
  • 1
    or change case $yn to case "${yn:-Y}" to have yes as default
    – rubo77
    Commented Apr 7, 2020 at 0:19
  • It's nice that your code works directly with if statements! But it doesn't seem to work in scripts where set -e is used. Do you got any workaround for that?
    – winklerrr
    Commented Dec 9, 2020 at 8:15
  • @winklerrr You'll have to elaborate. Seems to work fine with set -e. You sure you don't have an error somewhere else in your script?
    – mpen
    Commented Dec 9, 2020 at 9:01
  • In my solution I'm echoing the answer yes or no directly in the function which I tried to capture with a subshell like so: answer="$(promptyn "is the sky blue?")". I think that led to the "bug".
    – winklerrr
    Commented Dec 9, 2020 at 9:40
34

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
1
  • Seems like there is typo: > if [[ $do_xxxx =~ ^(y|Y|)$ ]] Should be: if [[ $do_xxxx =~ ^(y|Y)$ ]] Commented Oct 19, 2022 at 10:32
28

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.

23

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
22

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
  • 2
    @Jav the echo prints a newline after your response. Without it, the next thing to be printed would appear immediately after your response on the same line. Try removing the echo to see for yourself.
    – Dennis
    Commented Apr 1, 2014 at 16:10
22

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: https://www.ee.ryerson.ca/~courses/ele709/susv4/xrat/V4_xbd_chap07.html#tag_21_07_03_06

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 ""
7
  • 1
    I love the addition of a language agnostic option. Well done. Commented Sep 3, 2019 at 0:05
  • 1
    Unfortunately, POSIX only specifies the first two (yesexpr and noexpr). On Ubuntu 16, yesstr and nostr are empty.
    – Urhixidur
    Commented Jan 9, 2020 at 15:26
  • 2
    But wait! There's worse news! The bash case statement expressions are not regular, they are filename expressions. So Ubuntu 16's yesexpr and noexpr ("^[yY].*" and "^[nN].*", respectively) will fail utterly because of the embedded period. In a regular expression, ".*" means "any non-newline character, zero or more times". But in a case statement, it's a literal "." followed by any number of characters.
    – Urhixidur
    Commented Jan 9, 2020 at 16:46
  • 2
    Finally the best that can be done with yesexpr and noexpr in a shell environment, is use it in Bash's specific RegEx matching if [[ "$yn" =~ $yesexpr ]]; then echo $"Answered yes"; else echo $"Answered no"; fi
    – Léa Gris
    Commented Jan 9, 2020 at 17:17
  • I'm under SUSE Tumbleweed and local LC_MESSAGES displays ^[+1yY] ^[-0nN] yes no UTF-8. It's interesting that these regular expressions accept +/- and 0/1. Maybe they expect a robot to use the prompt.
    – foxesque
    Commented Jun 5, 2023 at 12:09
21

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
  • There is also a --defaultno argument to ensure the "No" option is selected by default.
    – WiMantis
    Commented Jul 29, 2021 at 13:15
16

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

curl -Ss https://example.com/installer.sh | 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
  • An important part of this is input validation. I think adapting my first example to accept tty input as you did would have done as well for you, and also gotten looping on bad input (imagine a few characters in the buffer; your method would force the user to always choose no). Commented May 22, 2014 at 13:03
13

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 https://unix.stackexchange.com/a/367880/143394 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 https://unix.stackexchange.com/q/383197/143394 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: http://unix.stackexchange.com/a/14444/143394
# 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
  • When I tested this script, instead of the outut above I got, Show dangerous command [y/N]? [y/n]? and Show dangerous command [Y/n]? [y/n]? Commented Jun 30, 2018 at 14:15
  • Thanks @IliasKarim, I fixed that just now.
    – Tom Hale
    Commented Jul 1, 2018 at 7:27
13

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
12
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
  • yap, -e -i don't work in sh (Bourne shell), but the question is tagged bash specifically..
    – Jahid
    Commented May 10, 2015 at 20:22
11

Lots of good answers to this question, but from what I can see none of them are my ideal, which would:

  1. Be simple, just a couple lines of shell
  2. Work with a single y/n keypress (no need to press enter)
  3. Default to yes if you just hit enter
  4. Work with an uppercase Y/N as well

Here's my version which does has those properties:

read -n1 -p "Continue? (Y/n) " confirm

if ! echo $confirm | grep '^[Yy]\?$'; then
  exit 1
fi

You can modify that conditional to only run on "yes" (just remove the ! in the if statement) or add an else if you want to run code on both branches.

2
  • 1
    Nicely done! Never too late to donate to an old question. I appreciate the single keypress (y, n, or enter) answer. I think you should include the else version, since my original question was for a full three branch question (if you count 'Cancel' as 'exit;;') Commented Jun 21, 2022 at 22:46
  • Thanks for this suggestion. What is the role of \? here?
    – Vendec
    Commented Jun 21 at 12:24
10

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.

8

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.

6

Check this

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

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).

5

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
5

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
4

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
}
4

The absolute most simple solution is this one-liner without clever tricks:

read -p "press enter ..." y

It reminds of the classic DOS Hit any key to continue, except that it waits for the Enter key, not just any key.

True, this does not offer you three options for Yes No Cancel, but it is useful where you accept control-C as No resp. Cancel in simple scripts like, e.g.:

#!/bin/sh
echo Backup this project
read -p "press enter ..." y
rsync -tavz . /media/hard_to_remember_path/backup/projects/yourproject/

because you don't like to need to remember ugly commands and paths, but neither scripts that run too fast, without giving you a chance to stop before you decide it is not the script you intended to run.

The command line argument y is required with sh and can optionally be used to receive the answer typed by the user before pressing the Enter key, like this:

echo You entered $y

With bash you may omit the last argument and just use:

read -p "press enter ..."
1
  • 1
    y isn't needed in read -p "press enter ..." y
    – rych
    Commented Apr 10, 2023 at 9:25
3
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
  • This seems complex and fragile. How about just yn(){ read -s -n 1 -p '[y/n]'; test "$REPLY" = "y" ; } yn && echo success || echo failure
    – tripleee
    Commented May 21, 2014 at 11:44
3

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
  • Not bad, but xargs --interactive is limited to yes or no. As long as that's all you need that can be enough, but my original question gave an example with three possible results. I really like that it is streamable though; many common scenarios would benefit from its ability to be piped. Commented Apr 9, 2015 at 0:25
  • I see. My thinking was that "cancel" meant to simply stop all further execution, which this supports via Ctrl-C, but if you need to do something more complex on cancel (or on no) this won't suffice. Commented Jun 7, 2015 at 22:21
3

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
  • Can you clarify the use of the prompt variable? Looks to me like it's wiped after the one liner, so how do you use the line to DO anything? Commented Jun 5, 2015 at 6:37
  • prompt is wiped after the while loop. Because I want the prompt variable to be initialized afterwards (since I am using the statement more often). Having this line in a shell-script will only proceed if y|Y is typed and exit if n|N is typed or repeat asking for input for everything else.
    – ccDict
    Commented Jun 5, 2015 at 6:42

Not the answer you're looking for? Browse other questions tagged or ask your own question.