when I use read statement in shell
read -n 1 -s -t 5 -p "Starting the script in 5 seconds. Press any key to stop!" yn
How to check if any key is pressed or not , if a key is pressed then the script must exit otherwise , the script must continue ?
You can use loop and limit read's time by one second:
#!/bin/bash
shouldStop=0
for (( i=5; i>0; i--)); do
printf "\rStarting script in $i seconds. Press any key to stop!"
read -s -n 1 -t 1 key
if [ $? -eq 0 ]
then
shouldStop=1
fi
done
if [ $shouldStop==1 ]
then
printf "do not run script"
else
printf "run script"
fi
Simply:
read -n 1 -s -t 5 -p "Starting the script in 5 seconds. Press any key to stop!" && \
exit 1
When a key is read, read returns 0 which would allow && to process the next statement exit 1. This would end your script.
You don't have to specify a variable as well so you wouldn't need it. read uses default variable $REPLY.
Related
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'
I have to write a script that prints every three seconds the % of CPU used by user or by system. If the user types a specific string, the output must switch back and forth.
How can I check for input without interrupting the output stream?
The read command would stop it, I'm running out of ideas.
You can use the read command with the timeout option read -t. For example the below script detects the string 'cc'. You can also specify the number of characters to read with -N so that user does not have to press enter.
flag=0
while true; do
sleep 3
if [[ $flag -eq 0 ]];then
echo user
else
echo sys
fi
read -t 0.25 -N 2 input
if [[ $input = "cc" ]] ; then
[[ $flag -eq 0 ]] && flag=1 || flag=0
fi
done
You can easily get information to your script by creating files. It sounds like your program has a loop and a sleep. Why not check for the existence of a file?
[ -r control.file ] && change_behavior
I need to create a while loop that continuously loops until a key press is registered, specifically the 'q' key for quit.
Here's my (failed) attempt:
while [ -z "$QUIT" ]
do
<Script stuff>
done &
read -sn 1 QUIT
export QUIT
However the while loop doesn't exit / end if I press any key. This is because $QUIT seems to only be accessible 'forwards' from where it is set, not backwards to the parent while loop section. Is there a way around this, or an alternative method for allowing my while loop to exit when a key (q if possible) is pressed?
Cheers.
I did it like this:
while true
do
# Suck in characters until we timeout, collecting sequence
seq=
while IFS= read -s -n1 -t 0.01 key; do
[ "$key" = "" ] && key=" "
seq="${seq}${key}"
done
# Do whatever processing for seq
done
Here's an example showing how to kill a background job
while true
do
echo "hello"
sleep 1
done &
while read -sn 1 QUIT && [[ $QUIT != 'q' ]];
do
echo "QUIT $QUIT"
done
# This kills the background job
kill %1
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)
I need to write an infinite loop that stops when any key is pressed.
Unfortunately this one loops only when a key is pressed.
Ideas please?
#!/bin/bash
count=0
while : ; do
# dummy action
echo -n "$a "
let "a+=1"
# detect any key press
read -n 1 keypress
echo $keypress
done
echo "Thanks for using this script."
exit 0
You need to put the standard input in non-blocking mode. Here is an example that works:
#!/bin/bash
if [ -t 0 ]; then
SAVED_STTY="`stty --save`"
stty -echo -icanon -icrnl time 0 min 0
fi
count=0
keypress=''
while [ "x$keypress" = "x" ]; do
let count+=1
echo -ne $count'\r'
keypress="`cat -v`"
done
if [ -t 0 ]; then stty "$SAVED_STTY"; fi
echo "You pressed '$keypress' after $count loop iterations"
echo "Thanks for using this script."
exit 0
Edit 2014/12/09: Add the -icrnl flag to stty to properly catch the Return key, use cat -v instead of read in order to catch Space.
It is possible that cat reads more than one character if it is fed data fast enough; if not the desired behaviour, replace cat -v with dd bs=1 count=1 status=none | cat -v.
Edit 2019/09/05: Use stty --save to restore the TTY settings.
read has a number of characters parameter -n and a timeout parameter -t which could be used.
From bash manual:
-n nchars
read returns after reading nchars characters rather than waiting for a complete line of input, but honors a delimiter if fewer than nchars characters are read before the delimiter.
-t timeout
Cause read to time out and return failure if a complete line of input (or a specified number of characters) is not read within timeout seconds. timeout may be a decimal number with a fractional portion following the decimal point. This option is only effective if read is reading input from a terminal, pipe, or other special file; it has no effect when reading from regular files. If read times out, read saves any partial input read into the specified variable name. If timeout is 0, read returns immediately, without trying to read any data. The exit status is 0 if input is available on the specified file descriptor, non-zero otherwise. The exit status is greater than 128 if the timeout is exceeded.
However, the read builtin uses the terminal which has its own settings. So as other answers have pointed out we need to set the flags for the terminal using stty.
#!/bin/bash
old_tty=$(stty --save)
# Minimum required changes to terminal. Add -echo to avoid output to screen.
stty -icanon min 0;
while true ; do
if read -t 0; then # Input ready
read -n 1 char
echo -e "\nRead: ${char}\n"
break
else # No input
echo -n '.'
sleep 1
fi
done
stty $old_tty
Usually I don't mind breaking a bash infinite loop with a simple CTRL-C. This is the traditional way for terminating a tail -f for instance.
Pure bash: unattended user input over loop
I've done this without having to play with stty:
loop=true
while $loop; do
trapKey=
if IFS= read -d '' -rsn 1 -t .002 str; then
while IFS= read -d '' -rsn 1 -t .002 chr; do
str+="$chr"
done
case $str in
$'\E[A') trapKey=UP ;;
$'\E[B') trapKey=DOWN ;;
$'\E[C') trapKey=RIGHT ;;
$'\E[D') trapKey=LEFT ;;
q | $'\E') loop=false ;;
esac
fi
if [ "$trapKey" ] ;then
printf "\nDoing something with '%s'.\n" $trapKey
fi
echo -n .
done
This will
loop with a very small footprint (max 2 millisecond)
react to keys cursor left, cursor right, cursor up and cursor down
exit loop with key Escape or q.
Here is another solution. It works for any key pressed, including space, enter, arrows, etc.
The original solution tested in bash:
IFS=''
if [ -t 0 ]; then stty -echo -icanon raw time 0 min 0; fi
while [ -z "$key" ]; do
read key
done
if [ -t 0 ]; then stty sane; fi
An improved solution tested in bash and dash:
if [ -t 0 ]; then
old_tty=$(stty --save)
stty raw -echo min 0
fi
while
IFS= read -r REPLY
[ -z "$REPLY" ]
do :; done
if [ -t 0 ]; then stty "$old_tty"; fi
In bash you could even leave out REPLY variable for the read command, because it is the default variable there.
I found this forum post and rewrote era's post into this pretty general use format:
# stuff before main function
printf "INIT\n\n"; sleep 2
INIT(){
starting="MAIN loop starting"; ending="MAIN loop success"
runMAIN=1; i=1; echo "0"
}; INIT
# exit script when MAIN is done, if ever (in this case counting out 4 seconds)
exitScript(){
trap - SIGINT SIGTERM SIGTERM # clear the trap
kill -- -$$ # Send SIGTERM to child/sub processes
kill $( jobs -p ) # kill any remaining processes
}; trap exitScript SIGINT SIGTERM # set trap
MAIN(){
echo "$starting"
sleep 1
echo "$i"; let "i++"
if (($i > 4)); then printf "\nexiting\n"; exitScript; fi
echo "$ending"; echo
}
# main loop running in subshell due to the '&'' after 'done'
{ while ((runMAIN)); do
if ! MAIN; then runMain=0; fi
done; } &
# --------------------------------------------------
tput smso
# echo "Press any key to return \c"
tput rmso
oldstty=`stty -g`
stty -icanon -echo min 1 time 0
dd bs=1 count=1 >/dev/null 2>&1
stty "$oldstty"
# --------------------------------------------------
# everything after this point will occur after user inputs any key
printf "\nYou pressed a key!\n\nGoodbye!\n"
Run this script