最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

tty - Bash arrow key detection in real-time - Stack Overflow

programmeradmin0浏览0评论

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:

  1. Why does holding down an arrow key for a couple of seconds cause the script to hang and increase CPU usage?
  2. Is there a better way to detect arrow key presses in real time without causing high CPU usage?
  3. 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:

  1. Why does holding down an arrow key for a couple of seconds cause the script to hang and increase CPU usage?
  2. Is there a better way to detect arrow key presses in real time without causing high CPU usage?
  3. 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
Add a comment  | 

1 Answer 1

Reset to default 3

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
发布评论

评论列表(0)

  1. 暂无评论