824

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?

Tom
  • 42,844
  • 35
  • 95
  • 101

10 Answers10

1369
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]$ ]]

Community
  • 1
  • 1
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • thanks, what do the double square brackets do? – Tom Dec 11 '09 at 03:02
  • and can that be negated? i.e. if not match, exit? – Tom Dec 11 '09 at 03:04
  • 2
    It has more features than `[]` including the regex match operator `=~`. See: http://mywiki.wooledge.org/BashFAQ/031 – Dennis Williamson Dec 11 '09 at 03:09
  • one small issue. If user enters anything that starts with y|Y, it will go through. [[ $a =~ "^[Yy]$" ]] – ghostdog74 Dec 11 '09 at 05:53
  • 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) – AlexChaffee May 21 '11 at 04:00
  • 1
    The read line should have the variable specified at the end: `read -p "Are you sure? " -n 1 -r REPLY` – Pascal Jun 12 '12 at 09:29
  • 22
    @tuner: `$REPLY` is automatically set if no variable name is supplied. – Dennis Williamson Jun 12 '12 at 09:42
  • @Dennis Williamson: with my cygwin-based bash 4.1.10(4) this didn't work somehow. – Pascal Jun 18 '12 at 08:37
  • @tuner: "didn't work" provides no information. Please describe exactly how it doesn't, because it should in every version of Bash from 3.2 to 4.2 something. – Dennis Williamson Jun 18 '12 at 09:18
  • @DennisWilliamson At first it wouldn't carry out the inner part of the if-clause. Checking again it works. Maybe something with the line endings in the first try. Sorry for the noise. – Pascal Jun 20 '12 at 07:20
  • 1
    Just to clarify, the `-r` option tells `read` not to interpret backslashes as escape characters. I understand it can also be used on the negated form above. – nandilugio Feb 26 '13 at 10:04
  • 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 – ErichBSchulz Apr 01 '13 at 07:49
  • @ErichBSchulz: But `sh` doesn't support either `[[` or `=~`. – Dennis Williamson Apr 01 '13 at 11:18
  • @DennisWilliamson that's exactly the problem! so if a "bash" script gets run under the old shell (which is possible) the negated form lurches past the test skips the exit... and proceeds straight onto the "dangerous stuff". – ErichBSchulz Apr 01 '13 at 23:59
  • 1
    @ErichBSchulz: Odd, I get `[[: not found` for both the negated and non-negated forms when run under `sh`. Which `sh` are you running? Oh, now I see what you mean. You're not talking about the syntax, but the logic. Yes, your point is very much valid! – Dennis Williamson Apr 02 '13 at 00:17
  • 3
    What about such a construct: `[[ $REPLY =~ ^[Yy]$ ]] || exit -1` How would this be evaluated by an older shell? – Scolytus Jun 04 '13 at 19:14
  • @Scolytus: That will work fine in any shell that supports all the features in use by that statement: double square bracket conditionals, `$REPLY` is the default variable for `read` and regex matching. Taken together, they are supported by Bash >= 3.2, ksh 93 (at least) and zsh (I don't know the version history). The `||` (logical or) construct is supported by most versions of most shells. – Dennis Williamson Jun 05 '13 at 00:44
  • 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 Jul 18 '13 at 17:02
  • 1
    Can I suggest to insert a `echo ""` just after `fi`? This will send to a new line the cursor, it's just for a better user experience :) Before this, the result output was: `Are you sure? nroot@server:` – Daniele Vrut Aug 21 '13 at 10:43
  • @DJHell: That's a good suggestion. I've added it to my answer. – Dennis Williamson Aug 21 '13 at 10:51
  • Can someone breakdown what `=~ ^[Yy]$` is doing/means in the if statement? `if [[ $REPLY =~ ^[Yy]$ ]]` – Tony Jul 18 '14 at 14:18
  • @Tony: `=~` is Bash's regex match operator. `^[Yy]$` is a regular expression which means a string which consists only of an upper or lower case y. – Dennis Williamson Jul 18 '14 at 15:03
  • what if I want it to be either y or press enter? – Jürgen Paul Nov 08 '14 at 10:20
  • @PineappleUndertheSea: With Bash 4, you can create a default: `read -p "Are you sure? " -r -i y -e` but the user would need to backspace to be able to change the "y" to an "n", for example. For Bash 3.2 and later, change the pattern in the match in the first example in my answer to `^([Yy]|)$` - this means to match upper or lower case "Y" or nothing (a blank line). – Dennis Williamson Nov 08 '14 at 14:16
  • @DennisWilliamson how would you add coloring for the message ? – angry kiwi Jun 15 '16 at 05:08
  • @angry_kiwi: Example: `yellow=$(tput setaf 3); reset=$(tput sgr0); read -p "${yellow}Name: ${reset}"` See `man 5 terminfo` in the Color Handling section for more information. Note that if you use `read -e` for readline support, you should use `\001` and `\002` to wrap the control sequences like this: `yellow=$(tput setaf 3); reset=$(tput sgr0); printf -v prompt "\001${yellow}\002Name: \001${reset}\002"; read -e -p "$prompt"` – Dennis Williamson Jun 15 '16 at 16:49
  • I'm getting conditional binary operator expected , syntax error near `=~' , `if [[ ! $REPLY =~ ^[Yy]$ ]]' ..... Oh looks like your example is Bash specific http://stackoverflow.com/questions/11206233/what-does-the-operator-do-in-shell-scripts – PHPDave Mar 29 '17 at 19:55
  • 1
    @PHPDave: Yes, it's Bash specific. The question is tagged [tag:bash] and "Bash" is in the question title. – Dennis Williamson Mar 29 '17 at 22:28
  • 1
    @SkippyleGrandGourou: I added some explanation. Please let me know if I missed something. – Dennis Williamson Feb 22 '19 at 19:19
  • In case anyone else has an issue using `read` with a file that has a `/bin/bash` shebang (while using a zsh terminal), this is what worked for me `choice=$(bash -c "read -p 'Are you sure? (y/n) ' -n 1 c; echo \$c")` – theOneWhoKnocks Aug 14 '19 at 04:37
  • @theOneWhoKnocks: You shouldn't need to do that. – Dennis Williamson Aug 14 '19 at 12:15
  • @DennisWilliamson I agree with you, but I had to none the less. – theOneWhoKnocks Aug 15 '19 at 03:54
  • From where does $REPLY variable come from can someone explain I am new to bash ? – Suhail Abdul Rehman Chougule Sep 24 '19 at 06:06
  • 1
    @SuhailAbdulRehmanChougule: [`$REPLY`](https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-REPLY) is the builtin variable that [`read`](https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-read) stores the string it reads in if you don't supply a variable name as an argument. I describe it in my answer in the first paragraph under "Explanation". – Dennis Williamson Sep 24 '19 at 12:30
  • Thanks for your reply @DennisWilliamson but i am getting an usual error "read: illegal option -n" – Suhail Abdul Rehman Chougule Sep 24 '19 at 18:04
  • Thanks for your reply @DennisWilliamson but i am getting an usual error "read: illegal option -n" THECODE IN NEXT COMMENT – Suhail Abdul Rehman Chougule Sep 24 '19 at 18:30
  • ``#!/bin/sh BRANCH=`git rev-parse --abbrev-ref HEAD` PROTECTED_BRANCH="master" MSG="$1" bug="bug-fix" if [ "$BRANCH" = "$PROTECTED_BRANCH" ]; then echo "inside branch if branch=$BRANCH and protected branch=$PROTECTED_BRANCH" if ! grep -qE "bug-fix" "$MSG"; then cat "$MSG" echo "Your commit message must contain the word 'bug-fix'" exit 1 else exit 0 fi else read -p "Are you sure you want to commit test (y/n) ?" -n 1 -r echo if [ "$REPLY" =~ ^[Yy]$ ] then exit 0 else echo "commit aborted" exit 1 fi fi`` – Suhail Abdul Rehman Chougule Sep 24 '19 at 18:31
  • @DennisWilliamson the issue got sorted out when instead of #! /bin/sh I used #! /usr/bin/env bash :) Any ways thanks I learned a lot :) – Suhail Abdul Rehman Chougule Sep 24 '19 at 18:40
  • @SuhailAbdulRehmanChougule: You're welcome. Yes, the shell invoked by `/bin/sh` (Often Dash) usually doesn't support Bash features. In this case, its simpler version of `read` doesn't support the `-n` option which, in Bash, limits the number of characters that `read` accepts. – Dennis Williamson Sep 24 '19 at 19:53
  • `syntax error near unexpected token `fi'` – Soonts Nov 08 '19 at 10:39
  • To allow a default of `y`, you can make the match group optional with `?`: `if [[ $REPLY =~ ^[yY]?$ ]]` – sshow Aug 06 '20 at 11:18
  • Vote down because using unusual `-r` and `-n`' – pylover Nov 14 '20 at 07:04
  • I like this better than what I found on the 'duplicate' Q/A – Merlin May 17 '21 at 04:51
  • I think `[[ ${REPLY^} = Y ]]` is slightly easier to understand than a regex. Good answer though. – Walf Aug 18 '21 at 05:07
  • Dangerous! If you accidentally hit the Y key twice this will propagate to the next part of the script that requests input, if there is one, which could be quite catastrophic. Probably wise to require the enter key be pressed to end any input. – Peter Kionga-Kamau Sep 17 '21 at 14:32
  • One-liner: `read -p "Continue? [Enter] → Yes, [Ctrl]+[C] → No."`. – Ctrl-C Oct 22 '21 at 07:15
  • `if [[ $REPLY =~ ^[Yy]$ ]]` is needed to proceed not `if [[ ! $REPLY =~ ^[Yy]$ ]]` – mikoop Oct 26 '21 at 03:21
  • Is this standard ? for me the `REPLY` variable gets the value `e` from the following `echo` then it complains about the unknown `cho` >_> – Enissay Dec 21 '21 at 19:52
  • @Enissay: You must be entering the commands (or pasting them) at the prompt instead of in a script. The `read` command would execute immediately, waiting for the next input which is the `echo` command. Since the `-n` option is set to accept one character then exit, the "e" goes into the `REPLY` variable and the rest of the line goes to the shell for execution. Since the "e" was read, the rest is "cho ..." which isn't a recognized command. If you want to try this from the command line, enter the `read` command, press a key, then enter the echo command. – Dennis Williamson Jun 28 '22 at 15:55
  • 1
    @mikoop: The negated tests proceeds to exiting/returning if something other that Y/y are pressed. If one of those keys is pressed the negated test will fail and execution proceeds past the `if` on to the rest of the script. One of the things this does is prevent one from having to wrap a whole script inside the `if`. – Dennis Williamson Jun 28 '22 at 16:03
  • Suggestion: Show the expected responses and which response is default. `read -p "Are you sure? [y/N] " -n 1 -r` And then, echo a newline unless they hit "Enter". `[[ -z $REPLY ]] || echo` – Bryan Roach Sep 21 '22 at 11:46
242

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.
jarno
  • 787
  • 10
  • 21
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • 10
    Good solution. Personally, if the bash script could really be crippling I like to have the person type out 'yes'. – SiegeX Dec 11 '09 at 05: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 Feb 13 '14 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 Feb 14 '14 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 – brettwhiteman Dec 10 '15 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 Oct 10 '16 at 16:26
69

Try the read shell builtin:

read -p "Continue (y/n)?" CONT
if [ "$CONT" = "y" ]; then
  echo "yaaa";
else
  echo "booo";
fi
kenorb
  • 155,785
  • 88
  • 678
  • 743
Adam Hupp
  • 1,513
  • 8
  • 7
58

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
SergioAraujo
  • 11,069
  • 3
  • 50
  • 40
  • Not accepting default Y – Gelldur Feb 20 '15 at 14:43
  • 1
    For default Y if [[ $response =~ ^(yes|y| ) ]] | [ -z $response ]; then – Gelldur Feb 20 '15 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 Jun 28 '18 at 20:42
32

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".
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Sébastien RoccaSerra
  • 16,731
  • 8
  • 50
  • 54
24

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
Tom
  • 42,844
  • 35
  • 95
  • 101
9
[[ -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.

Bradley
  • 91
  • 1
  • 3
8
echo are you sure?
read x
if [ "$x" = "yes" ]
then
  # do the dangerous stuff
fi
kingsindian
  • 183
  • 3
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.

Philip Schlump
  • 3,070
  • 2
  • 20
  • 17
3

qnd: use

read VARNAME
echo $VARNAME

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

zen
  • 12,613
  • 4
  • 24
  • 16