Bash script countdown timer needs to detect any key to continue - bash

I need to listen for any key press in a countdown timer loop. If any key is pressed then the countdown timer should break out of it's loop. This mostly works except for the enter key just makes the countdown timer go faster.
#!/bin/bash
for (( i=30; i>0; i--)); do
printf "\rStarting script in $i seconds. Hit any key to continue."
read -s -n 1 -t 1 key
if [[ $key ]]
then
break
fi
done
echo "Resume script"
I just can't seem to find any examples of how to detect that enter key anywhere online.

I think based on the return code of read, there is a work around for this problem. From the man page of read,
The return code is zero, unless end-of-file is encountered, read times out,
or an invalid file descriptor is supplied as the argument to -u.
The return code for timeout seems to be 142 [verified in Fedora 16]
So, the script can be modified as,
#!/bin/bash
for (( i=30; i>0; i--)); do
printf "\rStarting script in $i seconds. Hit any key to continue."
read -s -n 1 -t 1 key
if [ $? -eq 0 ]
then
break
fi
done
echo "Resume script"

The problem is that read would by default consider a newline as a delimiter.
Set the IFS to null to avoid reading upto the delimiter.
Say:
IFS= read -s -N 1 -t 1 key
instead and you'd get the expected behavior upon hitting the Enter key during the read.

Related

Bash read command - how to accept control characters

I am using the following to read a bunch of parameters from the user.
read -p "Name`echo $'\n> '`" NAME
All my parameters have default values. I want to provide an option to the user to skip providing values at any point. i.e. user might provide values for first 3 parameters and press Ctrl+s to skip entering values for the rest of them.
How can I trap Ctrl+s?
Ctrl+S is the terminal scroll lock character, and is not immediately available. You have two options:
Work with the system, use the system standard key combos, and make life easier for yourself and everyone else.
Fight the system, insist on using this key combo, do anything it takes to make it happen, and then live with the consequences.
If you want to work with the system, consider using a blank line and/or Ctrl+D, which is already extensively used to end input. This is easy and robust:
if read -p "Name (or blank for done)"$'\n> ' name && [[ $name ]]
then
echo "User entered $name"
else
echo "User is done entering things"
fi
Alternatively, here's a start for fighting the system:
#!/bin/bash
settings=$(stty -g)
echo "Enter name or ctrl+s to stop:"
stty stop undef # or 'stty raw' to read other control chars
str=""
while IFS= read -r -n 1 c && [[ $c ]]
do
[[ $c = $'\x13' ]] && echo "Ctrl+S pressed" && break
str+="$c"
done
echo "Input was $str"
stty "$settings"
This will correctly end when the user hits Ctrl+S on a standard configuration, but since it doesn't work with the system, it needs additional work to support proper line editing and less common configurations.
Not sure what you mean by trapping. The user can input Ctrl+S by typing Ctrl+V and then Ctrl+S, and your script can then do the check:
if [[ $NAME == '^S' ]]; then
...
# skip asking for more values and continue with default values
fi

Pause for loop until key is pressed

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.

bash, RANDOM generator with while read [duplicate]

I have a Bash script where I want to count how many things were done when looping through a file. The count seems to work within the loop but after it the variable seems reset.
nKeys=0
cat afile | while read -r line
do
#...do stuff
let nKeys=nKeys+1
# this will print 1,2,..., etc as expected
echo Done entry $nKeys
done
# PROBLEM: this always prints "... 0 keys"
echo Finished writing $destFile, $nKeys keys
The output of the above is something alone the lines of:
Done entry 1
Done entry 2
Finished writing /blah, 0 keys
The output I want is:
Done entry 1
Done entry 2
Finished writing /blah, 2 keys
I am not quite sure why nKeys is 0 after the loop :( I assume it's something basic but damned if I can spot it despite looking at http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO-7.html and other resources.
Fingers crossed someone else can look at it and go "well duh! You have to ..."!
In the just-released Bash 4.2, you can do this to prevent creating a subshell:
shopt -s lastpipe
Also, as you'll probably see at the link Ignacio provided, you have a Useless Use of cat.
while read -r line
do
...
done < afile
As mentioned in the accepted answer, this happens because pipes spawn separate subprocesses. To avoid this, command grouping has been the best option for me. That is, doing everything after the pipe in a subshell.
nKeys=0
cat afile |
{
while read -r line
do
#...do stuff
let nKeys=nKeys+1
# this will print 1,2,..., etc as expected
echo Done entry $nKeys
done
# PROBLEM: this always prints "... 0 keys"
echo Finished writing $destFile, $nKeys keys
}
Now it will report the value of $nKeys "correctly" (i.e. what you wish).
I arrived at the desired result in the following way without using pipes or here documents
#!/bin/sh
counter=0
string="apple orange mango egg indian"
str_len=${#string}
while [ $str_len -ne 0 ]
do
c=${string:0:1}
if [[ "$c" = [aeiou] ]]
then
echo -n "vowel : "
echo "- $c"
counter=$(( $counter + 1 ))
fi
string=${string:1}
str_len=${#string}
done
printf "The number of vowels in the given string are : %s "$counter
echo

Press any key to abort in 5 seconds

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

Reading input in bash in an infinite loop and reacting to it

So I want to wait 1 second for some input (options that i will implement later).
Or i want the program to print something(will implement later too). I have run into a problem tho, when trying to read that 1 char for the function, here is my code:
while true
do read $var -t 1
case $var in
("h")
help
;;
esac
done
If I try to echo after the case, the program does wait for 1 second, the problem is it doesnt recognise my h input, how would i fix that?
I've modified your sample slightly so that it works. There was an error in the read statement. use read varinstead of read $var. This corrected sample will now recognise also the h input.
Related to your question Why it doesn't wait the second (which was btw. hard to determine so i increased the timeout a bit ;-) )? This is because when you enter something, the read timeout is interrupted. It is as the parameter name say's a timeout for the user input. So if the user input's something the timeout is interrupted.
#!/bin/bash
while true
do
echo 'wait for input ...'
read -t 10 var
echo 'got input ...'
case $var in
h)
echo 'help'
;;
esac
done

Resources