handle user input on background - bash

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.

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

sleep like function with user input intterupt

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)

How to display the key pressed by the user?

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.

can anyone show me a very simple example of unix script loop

I just need an example of script that repeat all the same actions in a loop til we ask to stop it. Say I want the user to type y or n for exit, how would i implement it. I have something like
echo "Input y or n to exit"
read input
if [ "$input = y ]
then
.......
else
........
fi
For the same script demonstrated in the answer below or maybe other example, how can I have this addition to make the user control the script without having to exit only by pressing control+z
while true; do echo hello; sleep 1; done
will run until you send a signal.
while true; do
commands ...
read -p "Continue (y/n) ? " answer
case "$answer" in
Y*|y*) : ;;
*) break
esac
done
If the user responds with "Y" or "y", do nothing, in which case the loop continues. Otherwise break the loop.

Resources