26

How do I ask a yes/no type question in Bash?

I ask the question... echo "Do you like pie?"

And receive the answer... read pie

How do I do something if the answer is yes, or starts with y (so yes and yeah, etc, will work too).

Jahid
  • 21,542
  • 10
  • 90
  • 108
Ben Aubin
  • 5,542
  • 2
  • 34
  • 54
  • 2
    For zsh users, [the `-q` option is available](https://linux.die.net/man/1/zshbuiltins). _Read only one character from the terminal and set name to 'y' if this character was 'y' or 'Y' and to 'n' otherwise. With this flag set the return status is zero only if the character was 'y' or 'Y'. Note that this always reads from the terminal, even if used with the -p or -u or -z flags or with redirected input. This option may also be used within zle widgets._ – Ulysse BN May 15 '19 at 16:51
  • 1
    @UlysseBN zsh never ceases to amaze me! I don't remember what this original question was for, but I'm going to keep that in mind for when I have control of the shell executing my script - thank you! – Ben Aubin May 17 '19 at 16:01

6 Answers6

35

I like to use the following function:

function yes_or_no {
    while true; do
        read -p "$* [y/n]: " yn
        case $yn in
            [Yy]*) return 0  ;;  
            [Nn]*) echo "Aborted" ; return  1 ;;
        esac
    done
}

So in your script you can use like this:

yes_or_no "$message" && do_something

In case the user presses any key other than [yYnN] it will repeat the message.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Tiago Lopo
  • 7,619
  • 1
  • 30
  • 51
  • 1
    `read -p "$@ [y/n]: "` is incorrect, you need to use `$*` or the read will explode if the function is called with more than one argument. Also, technically this should use `yes_or_not "$@"` but for this that only matters if you use `yes_or_not 'foo bar'` and then the user doesn't input yes or no (the inner spaces will then get lost). – Etan Reisner Apr 03 '15 at 17:04
  • That's pretty awesome :) – Ben Aubin Apr 03 '15 at 18:43
  • I added a question mark after the `$*` ... This also works great with an if block: `if yes_or_no "Do next task"; then ;;;; fi` constructs – phyatt Jul 30 '20 at 16:04
  • I would make `yn` a local variable, just to avoid polluting the global scope... – onlycparra Jul 28 '23 at 18:07
16

This works too:

read -e -p "Do you like pie? " choice
[[ "$choice" == [Yy]* ]] && echo "doing something" || echo "that was a no"

Pattern starting with Y or y will be taken as yes.

Jahid
  • 21,542
  • 10
  • 90
  • 108
12

I like Jahid's oneliner. Here is a slight simplification of it:

[[ "$(read -e -p 'Continue? [y/N]> '; echo $REPLY)" == [Yy]* ]]

Here are some tests:

$ [[ "$(read -e -p 'Continue? [y/N]> '; echo $REPLY)" == [Yy]* ]] && echo Continuing || echo Stopping

Continue? [y/N]> yes
Continuing

$ for test_string in y Y yes YES no ''; do echo "Test String: '$test_string'"; echo $test_string | [[ "$(read -e -p 'Continue? [y/N]>'; echo $REPLY)" == [Yy]* ]] && echo Continuing || echo Stopping; done

Test String: 'y'
Continuing

Test String: 'Y'
Continuing

Test String: 'yes'
Continuing

Test String: 'YES'
Continuing

Test String: 'no'
Stopping

Test String: ''
Stopping

Update

In response to a comment, I'm going to add an adaptation to make this work in zsh.

Disclaimer

I would never write a shell script in zsh even though it is now my primary interactive shell. I still write all scripts in bash or sh. However, since you sometimes need to script modifications to your interactive shell (ex: source ~/dev/set_env), you might want to include prompting.

#! /usr/bin/env zsh
[[ "$(echo -n 'Continue? [y/N]> ' >&2; read; echo $REPLY)" == [Yy]* ]] \
  && echo Continuing \
  || echo Stopping
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
  • this only works on `bash` read not `zsh` read. – Akhil Jun 09 '22 at 03:50
  • 3
    @Akhil I added a `zsh` example for you. ☮️❤️ – Bruno Bronosky Jun 10 '22 at 02:51
  • appending `?` worked for me in `zsh`. i.e. `[[ "$(read -e '?Continue? [y/N]> '; echo $REPLY)" == [Yy]* ]]` ref: https://superuser.com/questions/555874/zsh-read-command-fails-within-bash-function-read1-p-no-coprocess – Akhil Jun 10 '22 at 05:35
6

This works:

echo "Do you like pie?"
read pie
if [[ $pie == y* ]]; then
    echo "You do! Awesome."
else
    echo "I don't like it much, either."
fi

[[ $pie == y* ]] tests to see of the variable $pie starts with y.

Feel free to make this better if you'd like.

Ben Aubin
  • 5,542
  • 2
  • 34
  • 54
4

In contrast to the other answers this function gives you the possibility to set a default:

function askYesNo {
        QUESTION=$1
        DEFAULT=$2
        if [ "$DEFAULT" = true ]; then
                OPTIONS="[Y/n]"
                DEFAULT="y"
            else
                OPTIONS="[y/N]"
                DEFAULT="n"
        fi
        read -p "$QUESTION $OPTIONS " -n 1 -s -r INPUT
        INPUT=${INPUT:-${DEFAULT}}
        echo ${INPUT}
        if [[ "$INPUT" =~ ^[yY]$ ]]; then
            ANSWER=true
        else
            ANSWER=false
        fi
}

askYesNo "Do it?" true
DOIT=$ANSWER

if [ "$DOIT" = true ]; then
    < do some stuff >
fi

On the command line you would see

Do it? [Y/n] y
NaN
  • 7,441
  • 6
  • 32
  • 51
0

Here is a short function:

prompt(){ read -p "$1" a; [ $a = "y" ]; }

Usage (if the answer is y then do_something executed):

prompt "Do you want it?"  &&  do_something

Usage with multiple commands:

prompt "Do you want it?" && {
  do_something1
  do_something2
}
hlorand
  • 1,070
  • 10
  • 8