How to pass a value to read input before being prompted? - shell

I have shell script prompting an answer y/n. at the prompt before giving input, I used control-c signal which calls Signal Handling function. In Signal Handling function there is a prompt "q" to exit or "y" and "y" should be used with the FIRST read prompt.
I tried to (( echo "y" | read )) but didn't work
==========================================
This is part of my script:
IntHandle ()
{
echo -e "\nUse 'q' to quit "
read var1
if [[ $var1 == q ]]
then
exit 1
else
echo "y" | read ----->here I need "y" to be an input to read prompt
directly and being saved in "ans" variable in
main body where I used control-c
fi
}
trap 'IntHandle' SIGINT
read -p "no valid user id entered, new user ids? [y\n]: " ans ----> here
used control-c signal before give y/n to ans
if [[ $ans == "y" ]]
then
read -p " username :" name
fi
.
.
.
.
.
================
output should be like below:
no valid user id entered, new user ids? [y\n]: #control-c entered
' Use 'q' to quit ' y ------> here "y" entered rather "q" in Siganl Handeling
function then it is saved in "ans" variable which gets
the condition true to prompt a username.
usernames: Larry -----> the name which is entered after true condition.
.
.
.

Instead of
echo "y" | read
Try:
ans=y
The definition of the trap function needs to be made before the trap is triggered. So I would move it to the top of the file.
When the function finishes, control is returned to the read statement that was interrupted by the Ctrl-C. So what I would do instead of echo "y" | read or ans=y is to reissue the original prompt:
IntHandle ()
{
echo -e "\nUse 'q' to quit "
read var1
if [[ $var1 == q ]]
then
exit 1
else
echo -n 'no valid user id entered, new user ids? [y\n]: '
fi
}

Related

Shell Script reading Enter key as an Input

I want to use the enter key as an input for a certain command.
But when there is no input I want to just loop.
If I write script like this then as the read command execution ends after 3 seconds with no input. it does not loop, but executes my desired output for enter key input command.
How can I fix this?
while true;
do
read -s -t 3 -n 3 key
if [ "${key}" == $'\0A' ]; then
#do something
elif [ "$key" == ""]; then
continue
fi
done
You have two problems, when using a character-literal in bash (it's bash-only), you need to use the actual-character, e.g.
if [ "${key}" == $'\n' ]; then
Next the equality comparison with [ ... ] is = not == (though bash will accept the latter). However, you must have a space before the closing ] in:
elif [ "$key" = "" ]; then
Making those changes the loop will loop continually with a 3-second timeout waiting for 3-characters of input in the manner you want, e.g.
#!/bin/bash
while true;
do
echo "looping"
read -s -t 3 -n 3 key
if [ "${key}" == $'\n' ]; then
echo "enter key"
elif [ "$key" = "" ]; then
continue
else
echo "key $key"
fi
done
Give it a go and let me know if you have further questions.
Edit Per Comment
If you want to execute a function on timeout, then you need to check the return of read. When a timeout occurs, the return will be greater than or equal to 128. If you then want to catch the Enter key, use an else and then check for an empty key, that will show that enter alone was pressed. Otherwise, you hae 3-chars in key, e.g.
#!/bin/bash
while true;
do
echo "looping"
read -s -t 3 -n 3 key
if [ $? -ge 128 ]; then
echo "timeout - execute function"
else
if [ "$key" = "" ]; then
echo "enter key"
continue
else
echo "key $key"
fi
fi
done
Example Use/Output
$ bash test.sh
looping
timeout - execute function
looping
timeout - execute function
looping
enter key
looping
timeout - execute function
looping
key foo
looping
enter key
looping
If I have the timeout/empty reversed, just switch the logic. I'm still not 100% clear on your comment, but I think this is what you indicated.
Since you want to re-ask the input if the user did not input an enter, I'd suggest to change the while true to something like while [[ "${key}" != $'\0A' ]]. THis way you can re-ask the input until it's an enter, and run your command after the while.
Would look something like:
# Read first time
read -s -t 3 -n 3 key
# If this wasn't an enter, keep asking until it is
while [[ "${key}" != $'\0A' ]]; do
read -s -t 3 -n 3 key
done
# Done
echo 'Running special command'

why is a bash read prompt not appearing when a script is run?

I have a bash script that prompts the user for different information based on what they're trying to do. The prompts are usually done with read -p. Usually it works just fine, the user sees what is being asked, enters what they need to enter, and everything does what it needs to do.
See the following (sanitized) snippet of a function in the script:
#!/bin/bash
function_name() {
if [ "$this_value" == "default" ];then
echo "Value set to default."
read -p "Enter desired value here: " desired_value
desired_value=${desired_value^^}
if [ "${#desired_value}" != 3 ] ;then
echo "$desired_value is an invalid entry."
exit 1
fi
if [ "$desired_value" != "$(some command that returns something to compare against)" ];then
echo "$desired_value is an invalid entry."
exit 1
fi
read -p "You entered $desired_value. Is this correct? [y/N] " reply
reply=${reply,,}
case "$reply" in
y|yes)
$some command that does what I want it to do
;;
*)
echo "User did not enter yes"
exit 1
;;
esac
fi
}
Usually the Enter desired value here and is this correct? lines appear just fine. But in a few instances I've seen, for some reason the read prompt is just blank. A user will see the following:
./script.bash
##unrelated script stuff
##unrelated script stuff
Value set to default.
user_entered_value_here
User did not enter yes. Exiting.
This is a real example that just happened that finally made me come here to ask what is going on (and I modified appropriately to make it an SO post).
What's happening is these two blank lines appear instead of the read -p text. For the first one, the user entered user_entered_value_here because they already know what is supposed to be entered there even without the read prompt. The second one, the Y/N prompt, they don't know, so they see it apparently hanging, and hit Enter instead of y, causing it to trigger the * case option.
I don't understand why the read -p text is not appearing, and especially why it's appearing for most users but not all users. I suspect there's some kind of environmental setting that causes this, but for the life of me I can't figure out what. This is being run only on RHEL 6.2, under bash 4.1.2.
I looked at the man of bash to catch some kind of detail about the read built-in. It is specified that -p option displays the "prompt on standard error, without a trailing newline, before attempting to read any input. The prompt is displayed only if input is coming from a terminal".
Let's consider the simple script input.sh:
#!/bin/bash
read -p "Prompt : " value
echo The user entered: "$value"
Example of execution:
$ ./input.sh
Prompt : foo
The user entered: foo
If stderr is redirected:
$ ./input.sh 2>/dev/null
foo
The user entered: foo
If the input is a pipe
$ echo foo | ./input.sh
The user entered: foo
If the input is a heredoc
$ ./input.sh <<EOF
> foo
> EOF
The user entered: foo
Rewrote your script with shell agnostic grammar and fixed some errors like comparing the string length with a string comparator != = rather than a numerical comparator -ne -eq:
#!/usr/bin/env sh
this_value=default
toupper() {
echo "$1" | tr '[:lower:]' '[:upper:]'
}
function_name() {
if [ "$this_value" = "default" ]; then
echo "Value set to default."
printf "Enter desired value here: "
read -r desired_value
desired_value=$(toupper "$desired_value")
if [ "${#desired_value}" -ne 3 ]; then
printf '%s is an invalid entry.\n' "$desired_value"
exit 1
fi
if [ "$desired_value" != "$(
echo ABC
: some command that returns something to compare against
)" ]; then
echo "$desired_value is an invalid entry."
exit 1
fi
printf 'You entered %s. Is this correct? [y/N] ' "$desired_value"
read -r reply
reply=$(toupper "$reply")
case $reply in
'y' | 'yes')
: "Some command that does what I want it to do"
;;
*)
echo "User did not enter yes"
exit 1
;;
esac
fi
}
function_name

How can I limit the input only to yes or no [duplicate]

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.

Bash script with user prompt

I am very new to bash scripting. I am trying to create a script which creates a local txt file that is treated as a database. The commands the user can use is: quit, add, delete, printdb. How would I create a user prompt? If the user inputs
quit how would I set up the quit function to quit the script and how would I pass the user input into the function?
I have done:
#!/bin/bash
func () {
echo -n "% "
read answer
}
if [ $answer != "quit" ] && [ $answer != "setdb" ] && [ $answer != "add" ] && [ $answer != "delete" ] && [
$answer != "printdb" ]
then
echo "Unrecognised command"
func
else
echo -n "%"
fi
#quit () {}
#setdb (){}
#add () {}
#delete () {}
#printdb () []
But it does not recognize if the commands are not valid.
This shows how you can use read and case to read input.
#!/bin/bash
while true; do
echo "Enter command."
read -r m
case $m in
quit) exit 0 ;; # exit without error
add) echo "New line" >> printdb.txt ;; # add new line
delete) sed -i '$d' printdb.txt ;; # delete last line
printdb) cat printdb.txt ;; # show file
*) echo "ERROR:Unknown command".; exit 1 ;; # handle invalid command
esac
done
References:
Prompt user for input with read.
How to program responses from read with case.
How to delete a line from a file by line number with sed.
Tool for getting suggestions while debugging your bash code.

sleep like function with user input intterupt

I wanted to do a shell function which countdown to n seconds (10 for exemple) and after that, continue the script.
I tried with the sleep function but it does stop the script entirely.
I want something like when the user input "y" during this countdown, it will stop the countdown and do something particular (much like an "interrupt").
And if the countdown finishes without any user input, the script continues.
Thank you !
**UPDATE* *
#Krzysztof Księżyk That's exactly what i wanted !
One difference, if i want the read return only if "Y" is the input how can i do that ? i already tried with the -d and -a...
Here is my code :
label="NTFS"
read -n 1 -t 5 MS
if [ -z "$MS" ]
then
echo "You don't input value, default will be taken"
else
echo -e "\nYou pressed 'Y' and want change default backup device."
read -p "Please input label of your secondary backup device: " secondary_label
label=$secondary_label
fi
echo "the choosen backup device label is $label"
You can use read command, eg.
read -n 1 -t 10
It will wait up to 10 second just for 1 character.
** UPDATE **
modified solution based on extra info from author
CNT=0
while [ $CNT -lt 10 ]; do
(( CNT++ ))
read -s -n 1 -t 1 -r KEY
if [ "$KEY" == 'y' ]; then
echo 'doing something'
else
echo 'doing nothing'
fi
done
If you want an interrupt, you probably want to trap SIGINT:
#!/bin/sh
trap : SIGINT
echo begin
for((i=0;i<10;i++)); do printf "$i\r"; sleep 1; done &
wait || kill $!
echo
echo end
The script counts to 10, but the timer aborts when the user sends a SIGINT (eg ctrl-C)

Resources