How to display the key pressed by the user? - bash

please help how to add variables in this script, does anyone know? Thank you all.
#!/bin/bash
ARROW_UP=??? # I do not know
ARROW_DOWN=??? # I do not know
ARROW_LEFT=??? # I do not know
ARROW_RIGHT=??? # I do not know
ARROW_ENTER=??? # I do not know
case "$KEY" in "$ARROW_UP") echo "press the up arrow key"
"$ARROW_DOWN") echo "press the down arrow key";;
"$ARROW_LEFT") echo "press the left arrow key"
"$ARROW_RIGHT") echo "press the right arrow key"
"$ARROW_ENTER") echo "press the enter key"
esac

Try something like this (you might want to add a case to break out of the loop):
#!/bin/bash
# Reset terminal to current state when we exit.
trap "stty $(stty -g)" EXIT
# Disable echo and special characters, set input timeout to 0.2 seconds.
stty -echo -icanon time 2 || exit $?
# String containing all keypresses.
KEYS=""
# Set field separator to BEL (should not occur in keypresses)
IFS=$'\a'
# Input loop.
while [ 1 ]; do
# Read more input from keyboard when necessary.
while read -t 0 ; do
read -s -r -d "" -N 1 -t 0.2 CHAR && KEYS="$KEYS$CHAR" || break
done
# If no keys to process, wait 0.05 seconds and retry.
if [ -z "$KEYS" ]; then
sleep 0.05
continue
fi
# Check the first (next) keypress in the buffer.
case "$KEYS" in
$'\x1B\x5B\x41'*) # Up
KEYS="${KEYS##???}"
echo "Up"
;;
$'\x1B\x5B\x42'*) # Down
KEYS="${KEYS##???}"
echo "Down"
;;
$'\x1B\x5B\x44'*) # Left
KEYS="${KEYS##???}"
echo "Left"
;;
$'\x1B\x5B\x43'*) # Right
KEYS="${KEYS##???}"
echo "Right"
;;
esac
done
More details here.

The short answer is you can't. Use a real programming language.
Some overly complex solutions can be found here, but I don't endorse them. The KEYBD trap solution at the bottom of the same page is good, but requires ksh93.

Related

Bash: how to stop a loop when a specific key is pressed, such as Q?

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

Bash script programming

I want to keep printing in a while loop(this occurs every second , i.e. sleeps for one second) and keep asking for a user input in a separate while loop, however if one indefinite runs , it doesn't go to the other for loop and if we run one in background and one in foreground still it doesnt helps, I am programming in bash script
#!/bin/bash
memusageStatus=false
diskspace=false
processStatus=false
menuStatus=true
function printMenu {
while [ true ]
do
if $menuStatus ; then
printf "\na) Show/Hide Memory Usage Information \nb) Show/Hide Disk Space Information \nc) Show/Hide Process Information\no) Show/Hide List of options \nq) Exit\n"
fi
sleep 1
tput cup 0 0 ;
tput ed
done
}
function disMenu {
while [ true ]
do
read ab
echo ab
case $ab in
'a') if $memusageStatus ; then
# free -k
memusageStatus=false;
else
memusageStatus=true;
fi
;;
'b') if $diskspace ; then
diskspace=false
else
diskspace=true;
fi
# df -h
;;
'c') if $processStatus ; then
processStatus=false
else
processStatus=true;
fi
;; #ps u
'o') echo "you pressed o"
if $menuStatus ; then
menuStatus=false;
else
menuStatus=true;
fi ;;
'q') exit 0;;
esac
done
}
printMenu &
disMenu
exit 0
You can try to use a read -t 1 in only one loop instead of a sleep 1.
But maybe with a longer read time.
Something like:
while [ true ]
do
echo "Show your Menu"
read -t 5 ab
case $ab in
'q') exit;;
esac
done

When using read with the -t flag, can I display the time remaining?

I have an input that times out after a few seconds, but to make it less jarring I would like it to display the time remaining. How can I do this?
I don't think it can be done with read -t, but this script accomplishes a countdown on another line as a background process:
#!/bin/bash
function displayCountdown {
((remaining=$1))
while [ $remaining -gt 0 ]
do
tput sc # save cursor pos
tput cuu1 # go up one line
tput cub 80 # go 80 chars left
tput el # clear to eol
( echo -n "$remaining second(s) remaining" ) >&2
tput rc # restore saved cursor pos
((remaining=remaining-1))
sleep 1
done
echo
}
NUM_SECONDS=5
## the first echo is needed
echo ; displayCountdown $NUM_SECONDS & read -t $NUM_SECONDS ; RC=$? ; kill -9 $! ; wait $! 2>/dev/null
echo "Got ($RC): $REPLY"
exit 0
It's not perfect. For instance, when pressing the delete key, it sometimes produces ^R's and messes up the terminal a bit. But, if you decide to use this, maybe there is some stty setting you can use to remedy this.
I tried this on a Mac. I haven't tried this on Linux.

handle user input on background

I want to handle user input, but in the background, like in a new thread.
For example, show a progress bar, and when the user hits R, the progress bar resets, or if the user hits Q, the script exits.
I don't want the script to wait for user input. Just render everything and if the user hits any key do something.
Is it posible in bash?
Thanks in advance.
EDIT: I need the script ALWAYS read user input but do not interrupt the execution of main loop.Complicated I make myself understood in English
_handle_keys()
{
read -sn1 a
test "$a" == `echo -en "\e"` || continue
read -sn1 a
test "$a" == "[" || break
read -sn1 a
case "$a" in
C) # Derecha
if [ $PALETTE_X -lt $(($COLUMNS-$PALETTE_SIZE)) ] ; then
PALETTE_X=$(($PALETTE_X+1))
fi
;;
D) # Izquierda
if [ $PALETTE_X -gt 0 ] ; then
PALETTE_X=$(($PALETTE_X-1))
fi
;;
esac
}
render()
{
clear
printf "\033[2;0f BALL (X:${BALL_X} | Y:${BALL_Y})"
_palette_render # Actualiza la paleta
_ball_render
}
while true
do
LINES=`tput lines`
COLUMNS=`tput cols`
render
_handle_keys
done
In my script, the ball moves (render>_ball_render) only when a key is pressed because _handle_keys wait for user input.
I made a ugly solution with read -t0.1 but don't like this
PD: Sorry for my last comment, the time edit finish in the middle of my editing
Here is a technique that seems to work. I am basing this on Sam Hocevar's answer to Bash: How to end infinite loop with any key pressed?.
#!/bin/bash
if [ ! -t 0 ]; then
echo "This script must be run from a terminal"
exit 1
fi
stty -echo -icanon time 0 min 0
count=0
keypress=''
while true; do
let count+=1
echo -ne $count'\r'
# This stuff goes in _handle_keys
read keypress
case $keypress in
# This case is for no keypress
"")
;;
$'\e[C')
echo "derecha"
;;
$'\e[D')
echo "izquierda"
;;
# If you want to do something for unknown keys, otherwise leave this out
*)
echo "unknown input $keypress"
;;
esac
# End _handle_keys
done
stty sane
If the stty sane is missed (e.g. because the script gets killed with Ctrl-C), the terminal will be left in a weird state. You may want to look at the trap statement to address this.
You might also add "reset" to the end of the script to reset the terminal into original state, or it might look locked. It will clear the screen as well, so one might want to add a pause before executing the command.

Bash: How to end infinite loop with any key pressed?

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

Resources