Press any key with timeout progress displayed in shell - bash

I want to provide user with a prompt to press any key or wait for a timeout to continue using shell. Usually this case is solved with following idiom:
read -rs -t3 -n1 "Press any key or wait to continue ..."
However this prompt seems a little bit clumsy to me and I would like to left only "Press any key to continue ..." part of a message and indicate timeout with dots printed each second. So I write following script:
#!/bin/sh
echo -n "Press any key to continue";
for _ in `seq 3`; do
if ! read -rs -n1 -t1 ; then echo -n "."; else break; fi
done
echo
It works just as I expect, but obviously there is too much code, so I have to put in separate file instead of using as sh -c "..." in script. Is there a way to implement it in more concise and compact way?
P.S. Returning non-zero error code on Ctrl-C pressed is a must.

A bit more concise :
echo -n "Press any key to continue";
for _ in {1..3}; do read -rs -n1 -t1 || printf ".";done;echo

Related

read builtin doesn't work with pipe

I'd like to ask user a confirmation to read from stdin (Display output [Y/n]). It works Ok if some arguments were provided, or no arguments were provided but there was some input. However, if some data was piped to the script, there's no confirmation.
#!/bin/bash
output_file=$(mktemp)
cleanup() {
rm -f "$output_file"
}
trap cleanup 0 1 2 3 15
if [ $# -gt 0 ]; then
while [ $# -gt 0 ]; do
echo "$1" >> "$output_file"
shift
done
else
while read -r line; do
echo "$line" >> "$output_file"
done
fi
while true; do
read -p "Display output? [Y/n]" response
if [ -z "$response" ]; then
break
fi
case $response in
[Yy]*) break;;
[Nn]*) exit;;
esac
done
less "$output_file"
What prevent read -p to work? What should be done to provide consistent behavior?
The read command reads input from standard in. If you have standard in fed from a pipe then read looks for its data from the pipe, not from your terminal.
On most platforms you can work around this by redirecting the read command's input directly from the tty device, as in:
read -p "Display output? [Y/n]" response </dev/tty
If the script read everything from standard input, what is the read -p going to get? And it likely doesn't prompt if the input is not an 'interactive device' (aka terminal). Have you checked the Bash man page for read? It says:
-pprompt
Display prompt, without a trailing newline, before attempting to read any input. The prompt is displayed only if input is coming from a terminal.
When your input is from a pipe, it is not from a terminal.

Ctrl-C to stop the shell script execution

I have a shellscript as follows. This doesn't terminates on pressing Ctrl-C. Can you guide me on how to modify the following code to kill the execution on Ctrl-C as input.
#!/bin/bash
validateURL()
{
regex='(https?|ftp|file)://[-A-Za-z0-9\+&##/%?=~_|!:,.;]*[-A-Za-z0-9\+&##/%=~_|]'
string=$1
if [[ $string =~ $regex ]]
then
echo "0"
else
echo "1"
fi
}
RED='\033[0;31m'
echo -n "Enter the URL :"
while read URL_REGISTRY
do
if [ $(validateURL $URL_REGISTRY) == "0" ]
then
break
else
echo -e "${RED}Wrong URL entered."
tput sgr0
echo -n "Enter the URL again :"
fi
done
The only way this can happen is if your shell blocks SIGINT. As per your description, your shell seems to do it. Reset the SIGINT in your shell so that your script receives SIGINT.
Run the following in your shell and run the script:
trap - SIGINT
Try:
stty intr "^C"
If that does not work, try:
stty -a
and figure out what is wrong with your settings. If you can't, update your answer with the output of stty -a.
Also make sure that you have not trapped the interrupt signal (2) as mentioned in the other answers.

Execute a command in a script and kill it when pressing a key

I want to write a bash script which records my voice until I press a concrete key. I have thought I could use this command
arecord -D hw -q -f cd -r 16000 speech.wav
which records from my laptop microphone and stops when the process is killed, but I don't know how to write bash code to call the process and then kill it when I press a concrete key. Can you help me?
key="q"
arecord speech.wav &
pid=$!
while read -n1 char ; do
if [ "$char" = "$key" ] ; then
kill "$pid"
break
fi
done
$! notation is the pid of last background job. the read builtin has the -n switch, with this switch only a number of characters instead of a full line is read at once.

SIGINT to cancel read in bash script?

I'm writting a bash wrapper to learn some scripting concepts. The idea is to write a script in bash and set it as a user's shell at login.
I made a while loop that reads and evals user's input, and then noticed that, whenever user typed CTRL + C, the script aborted so the user session ends.
To avoid this, I trapped SIGINT, doing nothing in the trap.
Now, the problem is that when you type CTRL + C at half of a command, it doesn't get cancelled as one would do on bash - it just ignores CTRL + C.
So, if I type ping stockoverf^Cping stackoverflow.com, I get ping stockoverfping stackoverflow.com instead of the ping stackoverflow.com that I wanted.
Is there any way to do that?
#!/bin/bash
# let's trap SIGINT (CTRL + C)
trap "" SIGINT
while true
do
read -e -p "$USER - SHIELD: `pwd`> " command
history -s $command
eval $command
done
I know this is old as all heck, but I was struggling to do something like this and came up with this solution. Hopefully it helps someone else out!
#/usr/bin/env bash
# Works ok when it is invoked as a bash script, but not when sourced!
function reset_cursor(){
echo
}
trap reset_cursor INT
while true; do
command=$( if read -e -p "> " line ; then echo "$line"; else echo "quit"; fi )
if [[ "$command" == "quit" ]] ; then
exit
else
history -s $command
eval "$command"
fi
done
trap SIGINT
By throwing the read into a subshell, you ensure that it will get killed with a sigint signal. If you trap that sigint as it percolates up to the parent, you can ignore it there and move onto the next while loop. You don't have to have reset_cursor as its own function but I find it nice in case you want to do more complicated stuff.
I had to add the if statement in the subshell because otherwise it would ignore ctrl+d - but we want it to be able 'log us out' without forcing a user to type exit or quit manually.
You could use a tool like xdotool to send Ctrl-A (begin-of-line) Ctrl-K (delete-to-end-of-line) Return (to cleanup the line)
#!/bin/bash
trap "xdotool key Ctrl+A Ctrl+k Return" SIGINT;
unset command
while [ "$command" != "quit" ] ;do
eval $command
read -e -p "$USER - SHIELD: `pwd`> " command
done
trap SIGINT
The please have a look a bash's manual page, searching for ``debug'' keyword...
man -Pless\ +/debug bash

How to implement a timer keypress in bash?

Here's what will happen, a message is displayed with a specified time waiting for keypress, if no keypress then it will resume.
Example
"Press ESC to exit, otherwise you will die.. 3..2..1"
"Press 'x' to procrastinate and check email, read some blogs, facebook, twitter.. otherwise you will resume work for 12 hours.. 3..2..1"
This should be a really handy function. How do I create this functionality in bash?
Look on the bash man page for the "read" command and notice the "-t timeout" option. Something like this should get you started
for i in 3 2 1 ; do
read -p $i... -n 1 -t 1 a && break
done
Use the -t and -n options of the read bash builtin command, also do not forget -r and -s. (see the manual for details)
#!/bin/bash
timeout=3
echo -n "Press ESC to exit, otherwise you will die..."
while [ $timeout -gt 0 ]; do
echo -n " $timeout"
if read -n1 -t1 -r -s x; then
echo
exit 0
fi
let timeout--
done
echo

Resources