How do I prompt a user for confirmation in bash script? [duplicate]
Asked Answered
E

10

840

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?

Enjoin answered 11/12, 2009 at 2:52 Comment(1)
RelatedMuscadine
M
1402
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]$ ]]

Muscadine answered 11/12, 2009 at 2:56 Comment(51)
thanks, what do the double square brackets do?Enjoin
and can that be negated? i.e. if not match, exit?Enjoin
It has more features than [] including the regex match operator =~. See: mywiki.wooledge.org/BashFAQ/031Muscadine
one small issue. If user enters anything that starts with y|Y, it will go through. [[ $a =~ "^[Yy]$" ]]Poindexter
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)Smilacaceous
The read line should have the variable specified at the end: read -p "Are you sure? " -n 1 -r REPLYTammeratammi
@tuner: $REPLY is automatically set if no variable name is supplied.Muscadine
@Dennis Williamson: with my cygwin-based bash 4.1.10(4) this didn't work somehow.Tammeratammi
@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.Muscadine
@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.Tammeratammi
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.Bullfinch
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 saferAsthenic
@ErichBSchulz: But sh doesn't support either [[ or =~.Muscadine
@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".Asthenic
@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!Muscadine
What about such a construct: [[ $REPLY =~ ^[Yy]$ ]] || exit -1 How would this be evaluated by an older shell?Jaredjarek
@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.Muscadine
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 scriptJoyless
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:Landahl
@DJHell: That's a good suggestion. I've added it to my answer.Muscadine
Can someone breakdown what =~ ^[Yy]$ is doing/means in the if statement? if [[ $REPLY =~ ^[Yy]$ ]]Gorgonian
@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.Muscadine
what if I want it to be either y or press enter?Mooneye
@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).Muscadine
@DennisWilliamson how would you add coloring for the message ?Pieria
@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"Muscadine
I'm getting conditional binary operator expected , syntax error near =~' , if [[ ! $REPLY =~ ^[Yy]$ ]]' ..... Oh looks like your example is Bash specific stackoverflow.com/questions/11206233/…Scala
@PHPDave: Yes, it's Bash specific. The question is tagged bash and "Bash" is in the question title.Muscadine
@SkippyleGrandGourou: I added some explanation. Please let me know if I missed something.Muscadine
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")Kermie
@theOneWhoKnocks: You shouldn't need to do that.Muscadine
@DennisWilliamson I agree with you, but I had to none the less.Kermie
From where does $REPLY variable come from can someone explain I am new to bash ?Pauiie
@SuhailAbdulRehmanChougule: $REPLY is the builtin variable that 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".Muscadine
Thanks for your reply @DennisWilliamson but i am getting an usual error "read: illegal option -n"Pauiie
Thanks for your reply @DennisWilliamson but i am getting an usual error "read: illegal option -n" THECODE IN NEXT COMMENTPauiie
#!/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 fiPauiie
@DennisWilliamson the issue got sorted out when instead of #! /bin/sh I used #! /usr/bin/env bash :) Any ways thanks I learned a lot :)Pauiie
@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.Muscadine
syntax error near unexpected token fi'`Phenacaine
To allow a default of y, you can make the match group optional with ?: if [[ $REPLY =~ ^[yY]?$ ]]Sandhurst
Vote down because using unusual -r and -n'Williawilliam
I like this better than what I found on the 'duplicate' Q/AKeneth
I think [[ ${REPLY^} = Y ]] is slightly easier to understand than a regex. Good answer though.Sodomy
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.Fulgurate
One-liner: read -p "Continue? [Enter] → Yes, [Ctrl]+[C] → No.".Ligulate
if [[ $REPLY =~ ^[Yy]$ ]] is needed to proceed not if [[ ! $REPLY =~ ^[Yy]$ ]]Eleen
Is this standard ? for me the REPLY variable gets the value e from the following echo then it complains about the unknown cho >_>Pimental
@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.Muscadine
@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.Muscadine
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 ]] || echoStrafe
P
250

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.
Poindexter answered 11/12, 2009 at 3:48 Comment(5)
Good solution. Personally, if the bash script could really be crippling I like to have the person type out 'yes'.Pyrimidine
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?Tabular
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?Etam
@Pyrimidine if it's really, really crippling you could have the user type out "The quick brown fox jumps over the lazy dogs" :PExcel
When the user input is invalid, how to ask the user to input again. And do the 'return;' if got invalid three times.Balsam
S
72

Try the read shell builtin:

read -p "Continue (y/n)?" CONT
if [ "$CONT" = "y" ]; then
  echo "yaaa";
else
  echo "booo";
fi
Spendthrift answered 11/12, 2009 at 2:58 Comment(0)
H
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
Heelpiece answered 12/5, 2013 at 14:53 Comment(3)
Not accepting default YDomash
For default Y if [[ $response =~ ^(yes|y| ) ]] | [ -z $response ]; thenDomash
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"; fiAmarillas
Z
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".
Zamir answered 17/7, 2013 at 9:3 Comment(0)
E
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
Enjoin answered 11/12, 2009 at 2:59 Comment(0)
H
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.

Homans answered 6/2, 2012 at 4:21 Comment(0)
P
8
echo are you sure?
read x
if [ "$x" = "yes" ]
then
  # do the dangerous stuff
fi
Phenanthrene answered 11/12, 2009 at 2:57 Comment(0)
E
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.

Ewall answered 11/12, 2009 at 2:59 Comment(0)
L
3

qnd: use

read VARNAME
echo $VARNAME

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

Linis answered 11/12, 2009 at 2:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.