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://username#www.example.com//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.
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://..
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
# http://stackoverflow.com/questions/1537673/how-do-i-forward-parameters-to-other-command-in-bash-script
else
exit 0
fi
Watch out for yes | command name here :)
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
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.
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://username#www.example.com//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
$
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)
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
}
read -r -p "Are you sure? [Y/n]" response
response=${response,,} # tolower
if [[ $response =~ ^(yes|y| ) ]] || [[ -z $response ]]; then
your-action-here
fi
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
}
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:
use local to prevent variable names from colliding
read use $2 $3 ... to control its action, so you may use -n and -t
if read exits unsuccessfully, echo a line feed for beauty
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).
use IPython-style "(y/[n])" to clearly indicate that "n" is the default.
This version allows you to have more than one case y or Y, n or N
Optionally: Repeat the question until an approve question is provided
Optionally: Ignore any other answer
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.
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
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"
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.
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.
Below code is combining two things
shopt -s nocasematch that will take care of case insensitive
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
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]? : "
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.
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
}
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.
Currently trying to write bash that will do the following.
check if curl is installed
print out "which curl" before running it so that the user is able to opt in/out of running something they consider unsafe.
Use case is when you download a big script from github and you want to have more control over what it is doing. Also to be more aware of how it works.
I am not sure how to include this opt in/out code without messing up the "return" echo. Maybe the answer is to use something different that the read -n 1 -s -r -p code. I like that solution because it allows hitting any key to continue.
To be clear. If I check for YES/NO later on, it is messed up because it will contain the character used to continue by pressing any key. In my output example the space bar was hit to continue
#! /bin/bash
# Returns YES if installed otherwise return NO
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press any key to approve running it"
read -n 1 -s -r -p "which curl"
echo ""
if which curl > /dev/null; then
echo "YES"
else
echo "NO"
fi
}
main() {
RESULT=$(check_curl_installed)
echo $RESULT
echo x${RESULT}x
}
main "$#"
exit 0
This is the output
user#computer:tmp$ ./check_curl_installed.sh
Before running, the command it will be printed below.
Press any key to approve running it
which curlYES
x YESx
Instead of using the output of the function, use its exit status.
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press any key to approve running it"
read -n 1 -s -r -p "which curl"
echo ""
if which curl > /dev/null; then
return 0
else
return 1
fi
}
if check_curl_installed
then
# do something
else
# do something else
fi
how about this modified version to get only the Y key to answer...
#! /bin/bash
# Returns YES if installed otherwise return NO
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press Y key to approve running it"
read -n 1 -r -s -p "Which curl?"
if [[ $REPLY =~ ^[Yy]$ ]]; then
if which curl > /dev/null ; then
printf "\nYES It is installed\n"
else
printf "\nNO It is not installed\n"
fi
else
printf "\nExiting - not checking\n"
fi
}
main() {
check_curl_installed
}
main "$#"
exit 0
Your echo result is, i think just pulling the first line of the check_curl_installed function...
Maybe if result was set to an array?
my testing around has shown that it's forgetting variables in the function at the higher main function. I even tried exporting to get the main function to work, but to no avail. I'm not super strong in bash, so i apologize on that.
Also might work better to put the echos inside each function, instead of shoving them into a variable...
Most, if not all, languages, only return one value from a function. Maybe this is why your output don't do as you want? a quick search brought this up https://unix.stackexchange.com/questions/408543/how-can-a-bash-function-return-multiple-values
Is it possible to prompt for user input when calling a function in bash?
Take this for example:
#!/bin/bash
test1(){
echo "Do you wish to install this program?"
select yn in "Yes" "No"; do
case $yn in
Yes ) make install; break;;
No ) exit;;
esac
done
}
pip list 2>/dev/null | if grep httplib2; then echo 2>/dev/null; else test1; fi
Disregard the fact I'm checking for httplib2 because I know that works fine if you else echo "Test". I've tried this with examples from stackoverflow and tldp, so I'm kind of confused now.
Can you not catch user input from a piped if statement?
Just tested with
pip list 2>/dev/null | if grep httplib2; then
echo 2>/dev/null;
else
echo "Type the year that you want to check (4 digits), followed by [ENTER]:"
read year
echo $year
fi
as well and had the same effect.
If test1 is in a pipeline but you need input from the terminal, use:
test1 </dev/tty
For example:
pip list 2>/dev/null | if grep httplib2; then echo 2>/dev/null; else test1 </dev/tty; fi
test1 gets its input from stdin. If you want to interact with it, its stdin must come from the terminal, /dev/tty, not from the pipeline.
An alternate method is to capture the handle for stdin before the pipeline starts. For example:
exec 3<&0; echo http | test1 <&3; exec 3<&-
Or,
exec 3<&0
pip list 2>/dev/null | if grep httplib2; then echo 2>/dev/null; else test1 <&3; fi
exec 3<&-
I'm writing a script to preform a repetitive task that only changes basic values and locations such as user name.
I've written up code that prompts for a username and verifies that it is not already in use. I'm now trying to prompt the user if the input the script received is correct and if it is not to change it. My issue is that if the input is correct it keeps looping. Anyone have any suggestions?
clear
confirm () {
# call with a prompt string or use a default
echo "CMIT # ${1}"
read -r -p "CMIT [Y/n/q] > " answer
case "${answer}" in
[yY]|[yY][eE][sS]) false ;;
[nN]|[nN][oO]) true ;;
[qQ]|[qQ][uU][iI][tT]) exit 1 ;;
esac
}
while true; do
OE_USER=
while (id -u $OE_USER > /dev/null 2>&1); do
echo "CMIT # What user will this run under?"
read -r -p "CMIT > " OE_USER
if id -u $OE_USER > /dev/null 2>&1; then
echo "CMIT # Bad User Name. Try Again"
fi
done
clear
confirm "Continue installing using '$OE_USER' as the server name?"
done
You can either declare a global flag that gets set inside the confirm function on a good answer, or you can use a return statement inside confirm that gets tested in a conditional in your while loop.
There are other options too, like using a recursive call after testing the user input. This would negate the need for the while loop, but create the need to make your initial input a function as well.
You can use the exit status of your function:
Change the "yes" case to execute "true", and the "no" case to execute "false". Then
while true; do
# ...
if confirm "Continue installing using '$OE_USER' as the server name?"
then
break
fi
done
clear
GO=true
confirm () {
# call with a prompt string or use a default
echo "CMIT # ${1}"
read -r -p "CMIT [Y/n/q] > " answer
case "${answer}" in
[yY]|[yY][eE][sS]) GO=false ;;
[nN]|[nN][oO]) GO=true ;;
[qQ]|[qQ][uU][iI][tT]) exit 1 ;;
esac
}
while $GO; do
OE_USER=
while (id -u $OE_USER > /dev/null 2>&1); do
echo "CMIT # What user will this run under?"
read -r -p "CMIT > " OE_USER
if id -u $OE_USER > /dev/null 2>&1; then
echo "CMIT # Bad User Name. Try Again"
fi
done
clear
confirm "Continue installing using '$OE_USER' as the server name?"
done
GO=true
I'm creating a script to update my linux distribution if I need to wipe the HD or I need to install Linux on another machine. So this script basically install all the programs I usually need. At the beginning a have a "read" command that asks if I want to install all the packages automatically or not. If I choose not, for each program not found it should ask me I want it to be installed and I use this code
if [[ $installall == "yes" ]]; then
echo " Installing $sciprog..."
sudo apt-get install -y $sciprog >/dev/null
{
scitest=`dpkg -s $sciprog | grep Status`
} 2>${HOME}/musthave.errorlog
if [[ $scitest != "Status: install ok installed" ]]; then
echo " I've encountered problems installing $sciprog that I can't resolve. "
echo " Consider installing $sciprog manually. "
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.notinstalled
else
echo " $sciprog installed correctly!"
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.installed
fi
else
echo " Seems like $sciprog is not installed... Do you want to download it?"
echo " Type 'y' for yes."
read secondyn ### THIS IS THE GUILTY COMMAND ###
if [[ $secondyn == "y" ]]; then
echo " Installing $sciprog ..."
sudo apt-get install -y $sciprog >/dev/null
{
checkinstall=`dpkg -s $sciprog | grep Status`
} 2>>${HOME}/musthave.errorlog
if [[ $checkinstall != "Status: install ok installed" ]]; then
echo " I've encountered problems installing $sciprog that I can't resolve. "
echo " Consider installing $sciprog manually. "
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.notinstalled
else
echo " $sciprog installed correctly!"
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.installed
fi
else
echo " Skipping $sciprog ..."
{
echo "=========="
echo " $sciprog"
} >>${HOME}/musthave.notinstalled
fi
### some more code which works as expected. All the code above is inside a
### while...do...done loop which reads line by line the file at the end
done <${HOME}/file.list
But if I run the script, it skips the "read" command in the else clause and assumes it to be "n"...
I can't figure out why, there are other read function also inside if...then...else...fi loops and they work as expected...
Any ideas?
The relevant portions of the code are still not complete but based on the comments I'm going to guess that your while loop looks like
while read -r ... ; do
# do stuff ...
# read user input
read -r var
done < file
From this the problem is immediately apparent: the inner read is getting its input from the same place as the outer loop, namely stdin which has been redirected from file, and not the user. For a slightly more portable alternative that does not depend on kernel-level support for /dev/tty, just use a different file descriptor other than stdin for the while loop.
while read -r ... <&9; do
# loop stuff
# stdin still attached to the terminal untouched,
# so this reads from the terminal as expected
read -r var
done 9< file
Notice that this example uses fd 9 for the file, leaving fd 0 (stdin) alone. Take a look at the BashFAQ 089 for more details.
Try reading from the controlling terminal device:
read secondyn </dev/tty
read secondyn < /proc/${PPID}/fd/0
That will look to the parent's input, which should still be stdin.