Bash KeyPress (or Read Single Character)

Hi,

I'm sorry if this has already been posted somewhere but I can't seem to find it on the forums (or anywhere on google :frowning: )

I am writing a script where a user must enter a single character to perform an action.

For example, Press Q to Quit or R to Refresh

Basically I am stuggling to come up with a way you can use the read function to read one character (without pressing enter or return) and then perform a command based on what character you entered.

The only solution I can find is read -n but this isn't available for me.

I've used the following code to perform a timed out read which will wait 1 second and then continue the code but it takes too long.

timedout_read() {
  timeout=$1
  varname=$2
  old_tty_settings=`stty -g`
  stty -icanon min 0 time ${timeout}0
  eval read $varname
  stty "$old_tty_settings"
}

timedout_read 1 TEST

Does anyone have any idea how to read single character without having to press enter?

I've found this piece of code in the past, it works with bash. Play around with it:

#!/bin/bash
 
_key()
{
  local kp
  ESC=$'\e'
  _KEY=
  read -d '' -sn1 _KEY
  case $_KEY in
    "$ESC")
        while read -d '' -sn1 -t1 kp
        do
          _KEY=$_KEY$kp
          case $kp in
            [a-zA-NP-Z~]) break;;
          esac
        done
    ;;
  esac
  printf -v "${1:-_KEY}" "%s" "$_KEY"
}
 
_key x
 

case $x in
  $'\e[11~' | $'\e[OP') key=F1 ;;
  $'\e[12~' | $'\e[OQ') key=F2 ;;
  $'\e[13~' | $'\e[OR') key=F3 ;;
  $'\e[14~' | $'\e[OS') key=F4 ;;
  $'\e[15~') key=F5 ;;
  $'\e[16~') key=F6 ;;
  $'\e[17~') key=F7 ;;
  $'\e[18~') key=F8 ;;
  $'\e[19~') key=F9 ;;
  $'\e[20~') key=F10 ;;
  $'\e[21~') key=F11 ;;
  $'\e[22~') key=F12 ;;
  $'\e[A' ) key=UP ;;
  $'\e[B' ) key=DOWN ;;
  $'\e[C' ) key=RIGHT ;;
  $'\e[D' ) key=LEFT ;;
  ?) key=$x ;;
  *) key=??? ;;
esac
 
echo "You have pressed $key"

try

echo -n "Enter character: "
stty cbreak
char=`dd if=/dev/tty bs=1 count=1 2>/dev/null`
stty -cbreak
echo " Character was: $char"

Thanks for the quick replies

Franklin52
When I try your method I get the following errors

./test.sh: read: illegal option: -d
read: usage: read [-r] [-p prompt] [-a array] [-e] [name ...]
./test.sh: printf: illegal option: -v
printf: usage: printf format [arguments]
You have pressed ???

funksen
When I try your method I get the following errors

unknown mode: cbreak
^[[B
unknown mode: -cbreak

It appears that I don't have the read -d, printf -v and cbreak functions.

Do you know of any other ways to do this?

try -icanon instead of -cbreak, if this doesn't work, use -raw

Edit: tried on AIX, raw worked

Thanks Funksen, raw works for me too!

---------- Post updated at 01:31 PM ---------- Previous update was at 12:54 PM ----------

Sorry to bother you again.

When I tried this previously, I just had a standard read command.
So I would press the key and then press return and the command would be fired.

Now that have the below code instead, I can't seem to capture the keypresses correctly

	stty raw
	sel=`dd if=/dev/tty bs=1 count=1 2>/dev/null`
	stty -raw

I used to have

read sel

case $sel in
		1)  KEY=1 ; NUM=$sel
	;;
		2)  KEY=2 ; NUM=$sel
	;;
		3)  KEY=3 ; NUM=$sel
	;;
		4)  KEY=4 ; NUM=$sel
	;;
		5)  KEY=5 ; NUM=$sel
	;;
		6)  KEY="" ; QUIT="Y" 
	;;
		["Q","q"]) KEY="" ; QUIT="Y" 
	;;
		$'\e[A' ) 
		if [ $NUM -eq 1 ]
		then
			NUM=6
		else
			NUM=$(($NUM - 1)) 
		fi
	;;
		$'\e[B' ) 
		if [ $NUM -eq 6 ]
		then
			NUM=1
		else
			NUM=$(($NUM + 1)) 
		fi
	;;
		"") KEY=$NUM 
	;;
		*) echo "ERROR"
	;;
	esac

This would determine whether the user was pressing up and down, q, a number or just pressing enter.

Now when I use the same code it isn't recognising the up, down or enter keypresses.

Have you any idea what I should change to get it to work.

Just to clarify, my code looks like the following.

	echo -n "Choose one: " 
	stty raw
	sel=`dd if=/dev/tty bs=1 count=1 2>/dev/null`
	stty -raw
		
	case $sel in
		1)  KEY=1 ; NUM=$sel
	;;
		2)  KEY=2 ; NUM=$sel
	;;
		3)  KEY=3 ; NUM=$sel
	;;
		4)  KEY=4 ; NUM=$sel
	;;
		5)  KEY=5 ; NUM=$sel
	;;
		6)  KEY="" ; QUIT="Y" 
	;;
		["Q","q"]) KEY="" ; QUIT="Y" 
	;;
		$'\e[A' ) 
		if [ $NUM -eq 1 ]
		then
			NUM=6
		else
			NUM=$(($NUM - 1)) 
		fi
	;;
		$'\e[B' ) 
		if [ $NUM -eq 6 ]
		then
			NUM=1
		else
			NUM=$(($NUM + 1)) 
		fi
	;;
		"") KEY=$NUM 
	;;
		*) echo "ERROR"
	;;
	esac

Any suggestions would be much appreciated.