Reputation: 21
I'm trying to detect arrow key presses in real-time (i.e., without waiting for input to be buffered) in a Bash script. I set the terminal to raw mode and attempt to read the escape sequences for arrow keys.
The script mostly works, but when I hold down an arrow key for a couple of seconds, the script seems to hang and CPU utilization spikes. My attempt to flush the buffer doesn’t seem to resolve the issue.
Here is a minimal reproducible example:
#!/usr/bin/env bash
if [ -t 0 ]; then
echo "We have a TTY on stdin."
else
echo "No TTY on stdin—arrow key detection won't work properly."
exit 1
fi
# Set terminal to raw mode so we can read arrow keys instantly
stty raw -echo
# Restore terminal settings on exit
cleanup() {
stty sane
echo "Exiting and restoring terminal."
}
trap cleanup EXIT
echo "Press arrow keys (or any key). Press Ctrl+C to exit."
while true; do
# Attempt to read up to 3 bytes with a 0.1s timeout
# (Arrow keys typically send 3-byte escape sequences)
if IFS= read -r -t 0.1 -n 3 keypress; then
case "$keypress" in
$'\e[A') echo "Up arrow!" ;;
$'\e[B') echo "Down arrow!" ;;
$'\e[C') echo "Right arrow!" ;;
$'\e[D') echo "Left arrow!" ;;
*) echo "Pressed: ${keypress} (not an arrow)" ;;
esac
else
# Nothing pressed in last 0.1s
:
fi
# Clear any other input waiting in the buffer
while IFS= read -r -t 0 -n 100000 _trash; do :; done
# Sleep a bit before checking again
sleep 0.2
done
I would like to know:
Any guidance or best practices on reading raw keypresses in Bash would be greatly appreciated.
Upvotes: 2
Views: 55
Reputation: 26727
Reading one character a time works better:
local state=0
while true; do
if IFS= read -r -t .1 -n 1 c; then
test "$c" = q && break
if test "$state" = 0 -a "$c" = $'\e'; then
state=1
elif test "$state" = 1 -a "$c" = '['; then
state=2
else
if test "$state" = 2; then
case "$c" in
A) echo "Up";;
B) echo "Down";;
C) echo "Right";;
D) echo "Left";;
esac
fi
state=0
fi
fi
done
Upvotes: 3