In Bash, how to add "Are you sure [Y/n]" to any command or alias?
Asked Answered
D

20

292

In this particular case, I'd like to add a confirm in Bash for

Are you sure? [Y/n]

for Mercurial's hg push ssh://[email protected]//somepath/morepath, which is actually an alias. Is there a standard command that can be added to the alias to achieve it?

The reason is that hg push and hg out can sound similar and sometimes when I want hgoutrepo, I may accidentlly type hgpushrepo (both are aliases).

Update: if it can be something like a built-in command with another command, such as: confirm && hg push ssh://... that'd be great... just a command that can ask for a yes or no and continue with the rest if yes.

Doggish answered 12/7, 2010 at 19:58 Comment(9)
You will probably want to use a function instead of an alias. From info bash: "For almost every purpose, shell functions are preferred over aliases."Morph
really? even for ones like ls -l or rm -i?Doggish
For almost every, not every. By the way, never do something like alias rm='rm -i'. One day, when you need it most, the alias won't be there and boom! something important will be lost.Morph
wait, if you don't have the alias for rm -i, then you cannot count on having a shell script as well. So do you always type rm -i every time?Doggish
It's about habits. You could create an alias like the one in my previous comment and be in the habit of typing rm and expecting the -i behavior, then one day the alias is not there (for some reason it gets unset or not set or you're on a different system) and you type rm and it goes ahead immediately deleting stuff without confirmation. Oops! However, if you did an alias like alias askrm='rm -i' then you'd be OK, since you'd get a "command not found" error.Morph
RelatedMorph
possible duplicate of How do I prompt for input in a Linux shell script?Alti
For the fish shell: stackoverflow.com/questions/16407530/…Averyaveryl
Also see https://mcmap.net/q/12175/-how-do-i-prompt-for-yes-no-cancel-input-in-a-linux-shell-script/3220113Caddis
M
440

These are more compact and versatile forms of Hamish's answer. They handle any mixture of upper and lower case letters:

read -r -p "Are you sure? [y/N] " response
case "$response" in
    [yY][eE][sS]|[yY]) 
        do_something
        ;;
    *)
        do_something_else
        ;;
esac

Or, for Bash >= version 3.2:

read -r -p "Are you sure? [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]
then
    do_something
else
    do_something_else
fi

Note: If $response is an empty string, it will give an error. To fix, simply add quotation marks: "$response". – Always use double quotes in variables containing strings (e.g.: prefer to use "$@" instead $@).

Or, Bash 4.x:

read -r -p "Are you sure? [y/N] " response
response=${response,,}    # tolower
if [[ "$response" =~ ^(yes|y)$ ]]
...

Edit:

In response to your edit, here's how you'd create and use a confirm command based on the first version in my answer (it would work similarly with the other two):

confirm() {
    # call with a prompt string or use a default
    read -r -p "${1:-Are you sure? [y/N]} " response
    case "$response" in
        [yY][eE][sS]|[yY]) 
            true
            ;;
        *)
            false
            ;;
    esac
}

To use this function:

confirm && hg push ssh://..

or

confirm "Would you really like to do a push?" && hg push ssh://..
Morph answered 12/7, 2010 at 20:36 Comment(14)
Actually it should be [y/N] and not [Y/n] for the current test.Centering
Worth mentioning that if you want to check not equal to on the second example, you should cast the $response variable. Eg: if [[ ! $response =~ ^([yY][eE][sS]|[yY])$ ]].Crosslink
@JoãoCunha: That would be "not match" rather than "not equal" since =~ is the regex match operator.Morph
@DennisWilliamson correct, that's what I tried to mean. Can't edit my comment for some reason.Crosslink
Nice general purpose yes/no function. supports a prompt strong, and optionally making 'y' or 'n' default.Isabelleisac
Fedora 21, CentOS 6 and Debian Wheezy run 4.2+ I deal with a lot of linux machines, I don't know of a single relevant linux distro not using 4.xTrumpeter
@Ray: Bash runs on a lot of non-Linux machines and there are a lot of older Linux versions in production. You might be surprised at how many systems are using Bash 3, and even 2.Morph
Use case "${response:-$2}" in to enable sending default answer to the function as confirm "Oh really [Yn]?" "Y" && do-nasty-thing. And perhaps use $REPLY instead of $response.Dionysian
For confirm, don't think you need the case statement stuff, just the test, and that will be the result of your function. (Line 1) read -r ... (Line 2) [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]Stavros
@BrianDuncan: The reason to use case is for versions of Bash or other shells which don't have regex matching. My answer already includes the regex version and says that the confirm function "would work similarly with the other two" matching options I show.Morph
@DennisWilliamson I see! Thanks for the clarification. I think what I was trying to say is that since we are only checking one boolean value, we shouldn't need a case statement. But it sounds like (and from what I've read) the case construct here provides the pattern-matching we couldn't get elsewhere in a widely compatible way. That's just shell-scripting I suppose :\Stavros
What is the purpose of [eE][sS]?Counteroffensive
@Counteroffensive [Yy][eE][sS] is a whole thing. So that case will pass the 'yes' in any case-sensitivity form of inputs like YES YEs YeS Yes yES yEs yeS yes. or simply if you can use Y or y also as | [yY] is combined in that.Jordans
@Isabelleisac Broken link. Nice general purpose yes/no function: current version; archived copy of orig.Petitionary
A
26

Here is roughly a snippet that you want. Let me find out how to forward the arguments.

read -p "Are you sure you want to continue? <y/N> " prompt
if [[ $prompt == "y" || $prompt == "Y" || $prompt == "yes" || $prompt == "Yes" ]]
then
  # https://mcmap.net/q/12178/-how-do-i-forward-parameters-to-other-command-in-bash-script
else
  exit 0
fi

Watch out for yes | command name here :)

Allynallys answered 12/7, 2010 at 20:4 Comment(0)
L
21

Confirmations are easily bypassed with carriage returns, and I find it useful to continually prompt for valid input.

Here's a function to make this easy. "invalid input" appears in red if Y|N is not received, and the user is prompted again.

prompt_confirm() {
  while true; do
    read -r -n 1 -p "${1:-Continue?} [y/n]: " REPLY
    case $REPLY in
      [yY]) echo ; return 0 ;;
      [nN]) echo ; return 1 ;;
      *) printf " \033[31m %s \n\033[0m" "invalid input"
    esac 
  done  
}

# example usage
prompt_confirm "Overwrite File?" || exit 0

You can change the default prompt by passing an argument

Layton answered 22/9, 2015 at 4:0 Comment(0)
O
14

To avoid explicitly checking for these variants of 'yes' you could use the bash regular expression operator '=~' with a regular expression:

read -p "Are you sure you want to continue? <y/N> " prompt
if [[ $prompt =~ [yY](es)* ]]
then
(etc...)

That tests whether the user input starts with 'y' or 'Y' and is followed by zero or more 'es's.

Ornithopod answered 12/7, 2010 at 20:33 Comment(1)
[yY]* would be the same as [yY](es)* in this case.Metaphysics
D
9

This may be a hack:

as in question In Unix / Bash, is "xargs -p" a good way to prompt for confirmation before running any command?

we can using xargs to do the job:

echo ssh://[email protected]//somepath/morepath | xargs -p hg push

of course, this will be set as an alias, like hgpushrepo

Example:

$ echo foo | xargs -p ls -l
ls -l foo?...y
-rw-r--r--  1 mikelee    staff  0 Nov 23 10:38 foo

$ echo foo | xargs -p ls -l
ls -l foo?...n

$
Doggish answered 3/8, 2010 at 21:15 Comment(0)
C
9

This may be a little too short, but for my own private use, it works great

read -n 1 -p "Push master upstream? [Y/n] " reply; 
if [ "$reply" != "" ]; then echo; fi
if [ "$reply" = "${reply#[Nn]}" ]; then
    git push upstream master
fi

The read -n 1 just reads one character. No need to hit enter. If it's not a 'n' or 'N', it is assumed to be a 'Y'. Just pressing enter means Y too.

(as for the real question: make that a bash script and change your alias to point to that script instead of what is was pointing to before)

Cocklebur answered 11/4, 2018 at 14:14 Comment(1)
This should pretty much be the accepted solution. Ts asked for [Y/n], but almost everyone else gave [y/N] answers.Roldan
M
4

Add the following to your /etc/bashrc file. This script adds a resident "function" instead of an alias called "confirm".


function confirm( )
{
#alert the user what they are about to do.
echo "About to $@....";
#confirm with the user
read -r -p "Are you sure? [Y/n]" response
case "$response" in
    [yY][eE][sS]|[yY]) 
              #if yes, then execute the passed parameters
               "$@"
               ;;
    *)
              #Otherwise exit...
              echo "ciao..."
              exit
              ;;
esac
}
Muliebrity answered 12/7, 2010 at 22:16 Comment(2)
Nice. Some minor corrections, though: you should put double-quotes around $response and $@ to avoid misparses, there are some redundant semicolons, and the * condition should return, not exit.Wimple
Just to be clear, it's the single semicolons at the end of some statements that are unnecessary.Morph
D
4
read -r -p "Are you sure? [Y/n]" response
  response=${response,,} # tolower
  if [[ $response =~ ^(yes|y| ) ]] || [[ -z $response ]]; then
      your-action-here
  fi
Dicrotic answered 14/7, 2010 at 13:17 Comment(2)
So a space character means yes?Alrich
All but 'yes or y' will confirm the action, so you are right! @xen2050Dicrotic
C
4

No pressing enter required

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"
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 no capitalisation in [y/n]?

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
}
Chasteen answered 28/6, 2018 at 6:26 Comment(0)
S
3

Well, here's my version of confirm, modified from James' one:

function confirm() {
  local response msg="${1:-Are you sure} (y/[n])? "; shift
  read -r $* -p "$msg" response || echo
  case "$response" in
  [yY][eE][sS]|[yY]) return 0 ;;
  *) return 1 ;;
  esac
}

These changes are:

  1. use local to prevent variable names from colliding
  2. read use $2 $3 ... to control its action, so you may use -n and -t
  3. if read exits unsuccessfully, echo a line feed for beauty
  4. my Git on Windows only has bash-3.1 and has no true or false, so use return instead. Of course, this is also compatible with bash-4.4 (the current one in Git for Windows).
  5. use IPython-style "(y/[n])" to clearly indicate that "n" is the default.
Seidel answered 13/9, 2016 at 5:53 Comment(0)
F
2

This version allows you to have more than one case y or Y, n or N

  1. Optionally: Repeat the question until an approve question is provided

  2. Optionally: Ignore any other answer

  3. Optionally: Exit the terminal if you want

    confirm() {
        echo -n "Continue? y or n? "
        read REPLY
        case $REPLY in
        [Yy]) echo 'yup y' ;; # you can change what you do here for instance
        [Nn]) break ;;        # exit case statement gracefully
        # Here are a few optional options to choose between
        # Any other answer:
    
        # 1. Repeat the question
        *) confirm ;;
    
        # 2. ignore
        # *) ;;
    
        # 3. Exit terminal
        # *) exit ;;
    
        esac
        # REPLY=''
    }
    

Notice this too: On the last line of this function clear the REPLY variable. Otherwise if you echo $REPLY you will see it is still set until you open or close your terminal or set it again.

Fatuous answered 26/9, 2019 at 17:12 Comment(0)
T
2

I like to exit as soon as possible if the user isn't sure, and I like the code to be readable and short. Depending on whether you'd like the user to press Return after their answer or not,

With pressing Return,

read -p "Warning: something scary: Continue (Y/N)? " reply
[ "$reply" != "Y" ] && [ "$reply" != "y" ] && echo "Aborting" && exit 1
echo "Scary thing"

or if you prefer not to wait for the user to press Return,

read -n1 -p "Warning: something scary: Continue (Y/N)? " reply
echo ""
[ "$reply" != "Y" ] && [ "$reply" != "y" ] && echo "Aborting" && exit 1
echo "Scary thing"

The other answers have the background on that -n1 flag and other options for read. The echo "" in the 2nd variant is to make subsequent output appear on a new line since the user doesn't have to press Return, so no newline has been echoed to the terminal. Also notice $reply being in quotation marks thus "$reply" to handle the situation where the user presses Return without specifying Y or N resulting in $reply being empty; the quotation marks prevent this breaking the tests, as discussed here.

Took answered 3/1, 2023 at 18:17 Comment(1)
@OscarVanL, Wouldn't it be to not proceed? That is what the code above is doing. Only case insensitive y is accepted.Mita
N
1

Late to the game, but I created yet another variant of the confirm functions of previous answers:

confirm ()
{
    read -r -p "$(echo $@) ? [y/N] " YESNO

    if [ "$YESNO" != "y" ]; then
        echo >&2 "Aborting"
        exit 1
    fi

    CMD="$1"
    shift

    while [ -n "$1" ]; do
        echo -en "$1\0"
        shift
    done | xargs -0 "$CMD" || exit $?
}

To use it:

confirm your_command

Features:

  • prints your command as part of the prompt
  • passes arguments through using the NULL delimiter
  • preserves your command's exit state

Bugs:

  • echo -en works with bash but might fail in your shell
  • might fail if arguments interfere with echo or xargs
  • a zillion other bugs because shell scripting is hard
Niello answered 2/2, 2013 at 1:52 Comment(0)
G
1

Try,

 #!/bin/bash
 pause ()
 {
 REPLY=Y
 while [ "$REPLY" == "Y" ] || [ "$REPLY" != "y" ]
 do
  echo -e "\t\tPress 'y' to continue\t\t\tPress 'n' to quit"
  read -n1 -s
      case "$REPLY" in
      "n")  exit                      ;;
      "N")  echo "case sensitive!!"   ;; 
      "y")  clear                     ;;
      "Y")  echo "case sensitive!!"   ;;
      * )  echo "$REPLY is Invalid Option"     ;;
 esac
 done
 }
 pause
 echo "Hi"
Gaggle answered 11/1, 2014 at 11:54 Comment(0)
D
0

This isn't exactly an "asking for yes or no" but just a hack: alias the hg push ... not to hgpushrepo but to hgpushrepoconfirmedpush and by the time I can spell out the whole thing, the left brain has made a logical choice.

Doggish answered 13/7, 2010 at 1:10 Comment(2)
But you know that you will use the TAB key!Allynallys
ok, true, but at least i will glance at the command and see it is weird and think twice before hitting EnterDoggish
Z
0

Not the same, but idea that works anyway.

#!/bin/bash  
i='y'  
while [ ${i:0:1} != n ]  
do  
    # Command(s)  
    read -p " Again? Y/n " i  
    [[ ${#i} -eq 0 ]] && i='y'  
done  

Output:
Again? Y/n N
Again? Y/n Anything
Again? Y/n 7
Again? Y/n &
Again? Y/n nsijf
$

Now only checks 1st character of $i read.

Ziagos answered 2/12, 2015 at 6:4 Comment(0)
J
0

Below code is combining two things

  1. shopt -s nocasematch that will take care of case insensitive

  2. and if condition that will accept both the input either you pass yes,Yes,YES,y.

    shopt -s nocasematch

    if [[ sed-4.2.2.$LINE =~ (yes|y)$ ]]

    then exit 0

    fi

Jotter answered 30/9, 2017 at 14:21 Comment(0)
U
0

Here is my solution that using localised regex. So in german also "j" for "Ja" would be interpreted as yes.

First argument is the question, if the second argument is "y" than yes would be the default answer otherwise no would be the default answer. The return value is 0 if the answer was "yes" and 1 if the answer was "no".

function shure(){
    if [ $# -gt 1 ] && [[ "$2" =~ ^[yY]*$ ]] ; then
        arg="[Y/n]"
        reg=$(locale noexpr)
        default=(0 1)
    else
        arg="[y/N]"
        reg=$(locale yesexpr)
        default=(1 0)
    fi
    read -p "$1 ${arg}? : " answer
    [[ "$answer" =~ $reg ]] && return ${default[1]} || return ${default[0]}
}

Here is a basic usage

# basic example default is no
shure "question message" && echo "answer yes" || echo "answer no"
# print "question message [y/N]? : "

# basic example default set to yes
shure "question message" y && echo "answer yes" || echo "answer no"
# print "question message [Y/n]? : "
Unrivalled answered 9/11, 2017 at 9:20 Comment(0)
Q
0

I know this is an old question but this might help someone, it hasn't been addressed here.

I have been asked how to use rm -i in a script which is receiving input from a file. As file input to a script is normally received from STDIN we need to change it, so that only the response to the rm command is received from STDIN. Here's the solution:

#!/bin/bash
while read -u 3 line
do
 echo -n "Remove file $line?"
 read -u 1 -n 1 key
 [[ $key = "y" ]] &&  rm "$line"
 echo
done 3<filelist

If ANY key other than the "y" key (lower case only) is pressed, the file will not be deleted. It is not necessary to press return after the key (hence the echo command to send a new line to the display). Note that the POSIX bash "read" command does not support the -u switch so a workaround would need to be sought.

Quartic answered 1/12, 2020 at 20:2 Comment(0)
S
0

Yes default base on Dennis Williamson answer

#!/bin/bash
confirm() {
    # call with a prompt string or use a default
    read -r -p "${1:-Are you sure?} [Y/n] " response
    case "$response" in
        @([nN])*([oO]))
            false
            ;;
        *)
            true
            ;;
    esac
}
Scalenus answered 26/12, 2022 at 7:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.