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:
- Why does holding down an arrow key for a couple of seconds cause the script to hang and increase CPU usage?
- Is there a better way to detect arrow key presses in real time without causing high CPU usage?
- How can I properly flush the input buffer to handle long key presses?
Any guidance or best practices on reading raw keypresses in Bash would be greatly appreciated.
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:
- Why does holding down an arrow key for a couple of seconds cause the script to hang and increase CPU usage?
- Is there a better way to detect arrow key presses in real time without causing high CPU usage?
- How can I properly flush the input buffer to handle long key presses?
Any guidance or best practices on reading raw keypresses in Bash would be greatly appreciated.
Share Improve this question asked Feb 5 at 20:15 suuudooosuuudooo 213 bronze badges 1- IMHO doing something like this in bash is a losing battle. Use a real programming language with a curses library. – Barmar Commented Feb 5 at 20:33
1 Answer
Reset to default 3Reading 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