849

I want to put a quick "are you sure?" prompt for confirmation at the top of a potentially dangerous bash script, what's the easiest/best way to do this?

1

10 Answers 10

1415
read -p "Are you sure? " -n 1 -r
echo    # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
    # do dangerous stuff
fi

I incorporated levislevis85's suggestion (thanks!) and added the -n option to read to accept one character without the need to press Enter. You can use one or both of these.

Also, the negated form might look like this:

read -p "Are you sure? " -n 1 -r
echo    # (optional) move to a new line
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
    [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell
fi

However, as pointed out by Erich, under some circumstances such as a syntax error caused by the script being run in the wrong shell, the negated form could allow the script to continue to the "dangerous stuff". The failure mode should favor the safest outcome so only the first, non-negated if should be used.

Explanation:

The read command outputs the prompt (-p "prompt") then accepts one character (-n 1) and accepts backslashes literally (-r) (otherwise read would see the backslash as an escape and wait for a second character). The default variable for read to store the result in is $REPLY if you don't supply a name like this: read -p "my prompt" -n 1 -r my_var

The if statement uses a regular expression to check if the character in $REPLY matches (=~) an upper or lower case "Y". The regular expression used here says "a string starting (^) and consisting solely of one of a list of characters in a bracket expression ([Yy]) and ending ($)". The anchors (^ and $) prevent matching longer strings. In this case they help reinforce the one-character limit set in the read command.

The negated form uses the logical "not" operator (!) to match (=~) any character that is not "Y" or "y". An alternative way to express this is less readable and doesn't as clearly express the intent in my opinion in this instance. However, this is what it would look like: if [[ $REPLY =~ ^[^Yy]$ ]]

51
  • 28
    after the "read" you should do an "echo" to get the cursor back on the next line. (just a raw "echo" with no params will do the trick) Commented May 21, 2011 at 4:00
  • 22
    @tuner: $REPLY is automatically set if no variable name is supplied. Commented Jun 12, 2012 at 9:42
  • 9
    just a warning on the negated form - if your user runs that in the old Bourne sh shell there is a risk that the read and conditional [[ will fail but the script will continue without exiting - perhaps not want you want - the positive version is therefore safer Commented Apr 1, 2013 at 7:49
  • 4
    What about such a construct: [[ $REPLY =~ ^[Yy]$ ]] || exit -1 How would this be evaluated by an older shell?
    – Scolytus
    Commented Jun 4, 2013 at 19:14
  • 7
    Just to point out - a rookie error in forgetting the hashbang at the top while testing this will throw errors. make sure you add #!/usr/bin/env bash (or similar valid hashbang) to the top of your script
    – nealio82
    Commented Jul 18, 2013 at 17:02
252

use case/esac.

read -p "Continue (y/n)?" choice
case "$choice" in 
  y|Y ) echo "yes";;
  n|N ) echo "no";;
  * ) echo "invalid";;
esac

advantage:

  1. neater
  2. can use "OR" condition easier
  3. can use character range, eg [yY][eE][sS] to accept word "yes", where any of its characters may be in lowercase or in uppercase.
5
  • 10
    Good solution. Personally, if the bash script could really be crippling I like to have the person type out 'yes'.
    – SiegeX
    Commented Dec 11, 2009 at 5:33
  • How can I use this to exit when the input is no (maybe echo "Quitting script."; exit;;), but if the input is yes, the script will just continue with whatever comes after esac?
    – Alaa Ali
    Commented Feb 13, 2014 at 23:53
  • 1
    If you do n|N ) echo "no"; return;; the script will end there if you say 'n' and continue with the rest otherwise, is that what you mean?
    – Rellikiox
    Commented Feb 14, 2014 at 13:02
  • 2
    @SiegeX if it's really, really crippling you could have the user type out "The quick brown fox jumps over the lazy dogs" :P Commented Dec 10, 2015 at 23:04
  • 4
    When the user input is invalid, how to ask the user to input again. And do the 'return;' if got invalid three times.
    – Bin
    Commented Oct 10, 2016 at 16:26
73

Try the read shell builtin:

read -p "Continue (y/n)?" CONT
if [ "$CONT" = "y" ]; then
  echo "yaaa";
else
  echo "booo";
fi
59

This way you get 'y' 'yes' or 'Enter'

 read -r -p "Are you sure? [Y/n]" response
 response=${response,,} # tolower
 if [[ $response =~ ^(y| ) ]] || [[ -z $response ]]; then
    your-action-here
 fi

If you are using zsh try this:

read "response?Are you sure ? [Y/n] "
response=${response:l} #tolower
if [[ $response =~ ^(y| ) ]] || [[ -z $response ]]; then
    your-action-here
fi
3
  • Not accepting default Y
    – Gelldur
    Commented Feb 20, 2015 at 14:43
  • 1
    For default Y if [[ $response =~ ^(yes|y| ) ]] | [ -z $response ]; then
    – Gelldur
    Commented Feb 20, 2015 at 14:48
  • 2
    In the regex, there is no need to match both yes and y, just y beginning is enough. Also, if default needs to be no, then the following would be helpful. read "response?Are you sure ? [Y/n] " && if [[ "$response" =~ ^[Yy] ]]; then echo "Yes, you did it."; else echo "No, narrow escape"; fi
    – Gopal
    Commented Jun 28, 2018 at 20:42
34

Here's the function I use:

function ask_yes_or_no() {
    read -p "$1 ([y]es or [N]o): "
    case $(echo $REPLY | tr '[A-Z]' '[a-z]') in
        y|yes) echo "yes" ;;
        *)     echo "no" ;;
    esac
}

And an example using it:

if [[ "no" == $(ask_yes_or_no "Are you sure?") || \
      "no" == $(ask_yes_or_no "Are you *really* sure?") ]]
then
    echo "Skipped."
    exit 0
fi

# Do something really dangerous...
  • The output is always "yes" or "no"
  • It's "no" by default
  • Everything except "y" or "yes" returns "no", so it's pretty safe for a dangerous bash script
  • And it's case insensitive, "Y", "Yes", or "YES" work as "yes".
0
26

This what I found elsewhere, is there a better possible version?

read -p "Are you sure you wish to continue?"
if [ "$REPLY" != "yes" ]; then
   exit
fi
10
[[ -f ./${sname} ]] && read -p "File exists. Are you sure? " -n 1

[[ ! $REPLY =~ ^[Yy]$ ]] && exit 1

used this in a function to look for an existing file and prompt before overwriting.

0
8
echo are you sure?
read x
if [ "$x" = "yes" ]
then
  # do the dangerous stuff
fi
4
#!/bin/bash
echo Please, enter your name
read NAME
echo "Hi $NAME!"
if [ "x$NAME" = "xyes" ] ; then
 # do something
fi

I s a short script to read in bash and echo back results.

3

qnd: use

read VARNAME
echo $VARNAME

for a one line response without readline support. Then test $VARNAME however you want.

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