Using this portion of a bash script as an example
{
read -p "Do you want to update the tv feed? [y/n/q] " ynq
case $ynq in
[Yy]* ) rm ~/cron/beeb.txt; /usr/bin/get-iplayer --type tv>>~/cron/beeb.txt;;
[Nn]* ) echo;;
[Qq]* ) exit;;
* ) echo "Please answer yes or no. ";;
esac
}
How do I get it so that you can press y and not have to press Enter for it to be accepted please?
Add -n 1 to the read command's options. From the bash manpage:
-n nchars
read returns after reading nchars characters rather than
waiting for a complete line of input.
BTW, you should also double-quote "$ynq" -- sometimes users will just press return, which can cause weird behavior if the variable isn't double-quoted. Also, note that read -n is a bash extension, so make sure you're using bash (i.e. #!/bin/bash or similar for the first line of the script), not a brand-x shell (#!/bin/sh or similar).
Use -n1 with read to specify max number of input length to 1:
read -n1 -p "Do you want to update the tv feed? [y/n/q] " ynq
I am on Mac and using read -n1 $user_decision doesn't do the trick for some reason in bash, sh, or zsh. So, I am using this which works across all:
#!/bin/zsh
# -k1 = First char pressed without waiting, for /r or /n.
# -t3 = timeout for 3 seconds
# -s = prevent outputting the input back to stdout.
echo "Press any letter..."
read -t3 -k1 -s user_decision
# Prints 1st arbitrary keypress entered during 3s timeout
echo $user_decision # EG: "y", or "n" for instance.
To simplify, you can just use read -k1 user_decision to get precisely what you requested set into the value for the variable name $user_decision, without waiting for /r or /n (hitting enter or return).
Related
How can I run a for loop which pauses after each iteration until a key is pressed?
for example, if I wanted to print the line number of file1, file2, file3, but only continuing each after pressing a key:
for f in dir/file? ; do wc -l $f ; pause until key is pressed ; done
Apologies if this is trivial, I'm new to the coding.
Use the read command to wait for a single character(-n1) input from the user
read -p "Press key to continue.. " -n1 -s
The options used from the man read page,
-n nchars return after reading NCHARS characters rather than waiting
for a newline, but honor a delimiter if fewer than NCHARS
characters are read before the delimiter
-s do not echo input coming from a terminal
-p prompt output the string PROMPT without a trailing newline before
attempting to read
#Stewart: Try:
cat script.ksh
trap "echo exiting...; exit" SIGHUP SIGINT SIGTERM
for file in /tmp/*
do
wc -l $file
echo "Waiting for key hit.."
read var
if [[ -n $var ]]
then
continue
fi
done
This script will be keep on running until/unless system get a signal to kill it (eg--> cntl+c etc). Let me know if this helps. Logic is simple created a trap first line of script to handle user's interruptions, then created a for loop which will look into a directory with all the files(you could change it accordingly to your need too). then wc -l of the current file as per yours post shown. Then ask user to enter a choice if user enters anything then it will go to loop again and again.
Hi I'm trying to implement an event that will happen after a 5 second countdown, unless a key is pressed. I have been using this code, but it fails if I press enter or space. It fails in the sense that enter or space is detected as "".
echo "Phoning home..."
key=""
read -r -s -n 1 -t 5 -p "Press any key to abort in the next 5 seconds." key
echo
if [ "$key" = "" ] # No Keypress detected, phone home.
then python /home/myuser/bin/phonehome.py
else echo "Aborting."
fi
After reading this post,
Bash: Check if enter was pressed
I gave up and posted here. I feel like there must be a better way than what I have tried to implement.
The read manual says:
The return code for read is zero, unless end-of-file is encountered
or read times out.
In your case, when the user hits any key within allowed time you wish to abort else continue.
#!/bin/bash
if read -r -s -n 1 -t 5 -p "TEST:" key #key in a sense has no use at all
then
echo "aborted"
else
echo "continued"
fi
Reference:
Read Manual
Note:
The emphasis in the citation is mine.
The accepted answer in the linked question covers the "detecting enter" component of the question. You look at the exit code from read.
As to handling spaces there are two answers.
The problem with space is that under normal circumstances read trims leading and trailing whitespace from the input (and word-splits the input) when assigning the input to the given variables.
There are two ways to avoid that.
You can avoid using a custom named variable and use $REPLY instead. When assigning to $REPLY no whitespace trimming or word-splitting is performed. (Though looking for this just now I can't actually find this in the POSIX spec so this may be a non-standard and/or non-portable expansion of some sort.)
Explicitly set IFS to an empty string for the read command so it doesn't perform and whitespace trimming or word-splitting.
$ IFS= read -r -s -n 1 -t 5 -p "Press any key to abort in the next 5 seconds." key; echo $?
# Press <space>
0
$ declare -p key
declare -- k=" "
$ unset -v k
$ IFS= read -r -s -n 1 -t 5 -p "Press any key to abort in the next 5 seconds." key; echo $?
# Wait
1
$ declare -p key
-bash: declare: k: not found
Hi im trying to pause the execution of a process so that a user cannot make multiple entries until 5 seconds have elapsed. I tried using sleep but sleep simply stops and then executes all the inputs the user ran while the process was asleep, i dont want there to be any input read from when the program was sleeping.
example of what i want: a chat bot
input 1: hi
output: "reply from program"
input 2 (before 5 seconds is up): "whats new"
-no output-
input 3 (5 seconds have passed): "how are you"
output: im fine.
example of what sleep command does:
input 1. "hi"
output "hey"
input 2 (before 5 seconds): "whats new"
-no output- waits
input 3: "how are you"
output: "not much is new"
output: "im fine"
You can read and throw away the user's input after the 5 seconds is up
# 1.
read -p "prompt 1: " first_thing
sleep 4
# this reads and ignores whatever the user has typed during the sleep
while read -t 1 _garbage; do :; done
# 2.
read -p "prompt 2: " next_thing
reads -t 1 option is a timeout of one second if there's nothing to read.
Testing
with input
$ read -p "prompt 1: " first_thing; sleep 4; while read -t 1 _garbage; do :;done ; read -p "prompt 2: " next_thing
prompt 1: foo
bar
baz
prompt 2: qux
$ echo $first_thing $next_thing
foo qux
no "extra" input before the 2nd read -- process does not "hang" awaiting input
$ read -p "prompt 1: " first_thing; sleep 4; while read -t 1 _garbage; do :;done ; read -p "prompt 2: " next_thing
prompt 1: hello
prompt 2: world
$ echo $first_thing $next_thing
hello world
This is by no means easy, and you need to be more specific about how you expect the script to respond to keyboard input.
There is no way to "lock" the keyboard; the user can continue punching keys as they see fit. If you are content for the keys to be echoed, but you want the input to be ignored, you could do something like the following:
# Beware! Read the entire answer; don't just use this command
timeout 5 bash -c 'while :;do read -s -d ""; done'
The timeout utility runs a command, killing it when the specified number of seconds have elapsed; the specified number may be a decimal fraction. The loop around the read command is necessary because the read would other terminate as soon as the Enter key is pressed; while :; do is a standard idiom for "loop forever".
The timeout command is part of Gnu coreutils. If you don't have it, perhaps because you are using a BSD derivative, you can probably find alternatives. There is a FreeBSD command, probably available on other BSDs including Mac OS X, called timelimit; I believe the correct invocation would be to replace timeout 5 with timelimit -t 5 -s9, but I don't have any easy way of testing.
You need to get the read command to actually read input immediately, as opposed to waiting until the Enter key is pressed. Otherwise, the typed input will still be available to the next command after the read is terminated.
There are several ways to do this. One is to use the -n 1 flag to cause the read to return after each character; another one is to use -d "" to set the end of input character to NUL, which has the side effect of putting read into character-at-a-time mode.
Also, you will probably want to suppress echo of the keys pressed while you are in the read loop. You can do that by adding the -s flag to the read command, but again that will have the side effect of leaving the terminal in "no echo" mode when the read command is interrupted. [Note 1]
Unfortunately, you'll probably find that the terminal settings have been permanently changed, because when read is killed by the timeout command, it doesn't have a chance to restore the terminal settings. So you'll end up with a terminal which doesn't echo, doesn't handle backspace and other line-editing commands, and doesn't honor Ctrl-D, amongst other issues.
To avoid this problem, you need to save and restore the terminal settings. You can do that with the stty command, as follows:
# Save the terminal settings
saved=$(stty -g)
# Ignore input for 5 seconds, suppressing echo
timeout 5 bash -c 'while :;do read -s -d ""; done'
# Restore the terminal settings
stty "$saved"
If you don't suppress echo, you'll find that your input prompt may appear on the same line as the ignored input. You could avoid that by outputting a "carriage return / erase to end of line" control sequence before the prompt:
tput cr; tput el; read -p "Give me some input: "
The answer of #glenn is a good answer!
Maybe you don't know in front how long you are going to sleep (you are calling
some other functions).
When you just want to flush the input queue, you can try a similar approach:
echo "What is your name?"
read name
echo "Please be quiet for 5 seconds"
sleep 5
smalltime=0.000001
read -t ${smalltime} -s garbage
while [ -n "${garbage}" ]; do
echo "Flushing $garbage"
read -t ${smalltime} -s garbage
done
echo "Hello ${name}, please say something else"
read y
echo "You said $y"
I am trying to send lines from terminal to a text file multiple times using the following script. After writing the first line and its description in 2nd line, the script asks user whether he wants to enter another line or not. If yes, then user writes the 3rd line, 4th line and so on...
my problem is that after 2nd line, i.e. starting from 3rd line, the script writes only the first word, not the full sentence. How do I solve this ?
function ml() {
echo $# >> $HOME/path/to/file/filename
echo -n "Enter description and press [ENTER]: "
read description
echo -e '\n[\t]' $description >> $HOME/path/to/file/myfile
while true
do
read -p "Add another line?y?n" -n 1 -r
echo -e "\n"
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo -n "Enter another line and press [ENTER]: "
read -a meaning
echo -e "[\t]" $meaning >> $HOME/path/to/file/myfile
else
break
fi
done
echo % >> $HOME/path/to/file/myfile
}
also I would like to have another modification in the code
read -p "Add another line?y?n" -n 1 -r
instead of asking y/n input, can it be done that after inserting the first two line, every ENTER will ask for another line input and pressing ESCAPE will terminate the script?
This is because in your second call to read, you are using the -a argument which does:
The words are assigned to sequential indices of the array variable aname, starting at 0. aname is unset before any new values are assigned. Other name arguments are ignored.
That appears to be not what you want.
I want to write this bash loop for zsh
while true; do echo "print something"; read -p "pause"; done
This loop echos, then waits for the user to press enter. If I enter it as is, the read statement doesn't pause, causing zsh to infinitely echo "print something" without waiting for the user to press enter.
In zsh:
read -s -k '?Press any key to continue.'
From man zshbuiltins:
-s Don't echo back characters if reading from the terminal.
-k Read only one character.
name?prompt Name is omitted, thus user input is stored in the REPLY variable (and we ignore it). The first argument contains a ?, thus the remainder of this word is used as a prompt on standard error when the shell is interactive.
To include a newline after the prompt:
read -s -k $'?Press any key to continue.\n'
$'' is explained under QUOTING in man zshmisc.
Finally, a pause function that takes an arbitrary prompt message in a script that does what the OP asks:
#!/usr/bin/env zsh
pause() read -s -k "?$*"$'\n'
while true; do
echo "print something"
pause "pause"
done
Since this is about the only search result I could find, and I found it helpful but still a bit confusing, here is another way of putting it: If all you want to do is echo a line of text and wait for the user to press enter ...
read \?"I am waiting for you to press [Enter] before I continue."
It looks like -p does something different in zsh. You will probably need something like read some_variable\?pause.
#!/bin/zsh
pause()
{
echo "$*"; read -k1 -s
}
now we can call the function with any prompt text:
pause "paused! press any key to continue"
pause "you can write anything here :)"
If you want a way that works in both bash and zsh, and ensures I/O to/from the terminal:
# Prompt for a keypress to continue. Customise prompt with $*
function pause {
>/dev/tty printf '%s' "${*:-Press any key to continue... }"
[[ $ZSH_VERSION ]] && read -krs # Use -u0 to read from STDIN
[[ $BASH_VERSION ]] && </dev/tty read -rsn1
printf '\n'
}
export_function pause