I know i can read within a script something into a variable, like this: variable = read But i have to press enter in order to submit the value into the variable. What do i have to know in order to submit the value of a keypress into a variable without hitting enter, or if not submitting into a variable just to react when a certain key is pressed ?
-
first ask user to enter the variable value then validate that value accordingly – Praduman Feb 12 '15 at 13:45
2 Answers
With bash
, you can use the -n
parameter to the built-in read
function to limit the number of characters read without requiring a newline:
#!/bin/bash
echo "Ready? [Y/n]: "
read -n 1 y_or_n
echo
case "$y_or_n" in
[Yy]|"")
echo "you said yes"
;;
*)
echo "you said no"
;;
esac
This works whether bash
is invoked as sh
or bash
.
See help read
or the bash
manpage for more details.
Note that other shells may not support the -n
parameter to read
. dash
, for example, does not.

- 1,523
- 9
- 10
-
1Thanks, fixed.
sh
could be linked todash
or another shell that doesn't supportread -n
. – zackse Mar 01 '15 at 17:04
The thing about the shell's read
is - whether you limit its total byte count per read
or not - it's still not going to get you a keypress per read
. It's only going to get some fixed number of characters per read
. In order to get a keypress per read
you need to block out your input - and the best way to do that is probably dd
. This is a shell function I wrote a couple of months ago when I was experimenting w/ dd
blocking on terminal i/o:
printkeys()( IFS= n=1
set -f -- "$(tty <&2)"
exec <${1##[!/]*};set \\t
_dd()( c=conv=sync,unblock i=ibs=72 b=bs=8
dd bs=7 ${c%,*} | dd $i o$b c$b $c
) 2>/dev/null
trap " trap '' TTOU TTIN
stty '$(stty -g
stty raw isig inlcr)' " INT EXIT
_dd |while ${2:+:} read -r c || exit 2
do case $#:$c in (1:): ;;
(*:?*) set ":%d$@" \
\'${c%"${c#?}"} ;
c=${c#?} ;;
(*) printf "$@" ;
[ 0 -eq $((n=n<4?n+1:0)) ] &&
set '\n\r'||set \\t ;; esac
done
)
You see, in the above case, the terminal input is filtered on two dd
processes in a pipeline. The terminal is set with stty
to raw mode in a trap
which also restores the terminal state on INT
errupt or EXIT
to whatever it was when the function was called (which can be done with CTRL+C probably, or whatever your interrupt key is). In raw mode a terminal flushes input to any reader as soon as it arrives - keypress by keypress. It doesn't just push a byte at a time; it pushes the entire buffer as soon as it possibly can. And so when you press the UP arrow, for example, and your keyboard sends an escape sequence like:
^[[A
...which is three bytes, and it pushes that all at once.
dd
is spec'd to satisfy any read as soon as it is offered - no matter how high you set its ibs=
. This means that, though I set it with ibs=7
, when the terminal pushes only three bytes a read is still completed. Now that's difficult to handle with most any other utility, but dd
's conv=sync
fills out the difference w/ \0NUL
bytes. So when you push the UP arrow on your keyboard, dd
reads three bytes and writes 7 to the next dd
- the three bytes in the escape sequence and 4 more \0NUL
s.
But in order to pull that data in with the shell's read you've gotta block it again, so the next dd
syncs out its input buffer to 72 bytes - but it also conv=unblock
s it. With the unblock
conversion, dd
splits its input on \n
ewline delimiters for its cbs=
count - which is here 8. With sync
and unblock
(or block
) conversions, dd
doesn't synchronize on \0NUL
s, but rather on trailing spaces. So for every 7 bytes the first dd
writes to the pipe between them, the second dd
buffers 72 bytes - the first few are whatever the keypress was, then \0NUL
s, then 65 spaces at the tail of each read.
The other thing unblock
does is elide trailing spaces - it will eat as many spaces as might occur at the tail-end of each of its cbs=
conversion blocks. So because dd
writes output at obs=8
bytes per write, it writes 9 lines per read in 2 total writes to the output pipe. The first write is the first line and consists of the 7 bytes read from the input pipe and a trailing newline - one more byte. The next write is 8 newlines - all at once and in a row - 8 more bytes - because dd
eats all 8 spaces for each of those 8 conversion blocks.
On the other side of those two dd
s a shell while
loop read
s line by line. It can ignore blank lines - because the terminal is converting all newlines to carriage returns on output anyway according to the inlcr
stty
option. But when it detects even a single byte in $c
after the shell's input is read
into its value, then you have a keypress - and a whole one too, so long as your keyboard is not sending more than 7 bytes per keystroke (though that would just need a different blocking factor).
When the shell has a keypress in $c
it iterates over it byte for byte and splits it out into an array divided by '
characters, then printf
s the decimal values for every byte in $c
all at once. If you run that function, you should get output like this:
a:97 b:98 c:99 d:100 e:101
f:102 ;:59 ^M:13 ^M:13 ^M:13
s:115 a:97 d:100 f:102 :32
':39 ':39 ':39 a:97 s:115
d:100 f:102 ;:59 ':39 ^[[A:27:91:65
^[[D:27:91:68 ^[[B:27:91:66 ^[[C:27:91:67 ^[[D:27:91:68 ^[[C:27:91:67
^[[A:27:91:65 ^[[D:27:91:68 ^[[C:27:91:67 ^[[B:27:91:66 ^[[D:27:91:68
^[[C:27:91:67 ^[[A:27:91:65 ^[[D:27:91:68 ^[[C:27:91:67 ^[[B:27:91:66
Because the \0NUL
s that dd
inserts to block out the keypresses evaporate as soon as the shell fills out a variable's value with its input - you can't put \0NUL
s in a shell variable (in any shell but zsh
- in which case it is still configurable that way). As far as I know this should work in any shell - and it definitely does work in bash
, dash
, and ksh93
. It might even reliably handle multibyte input - but I won't swear to it.
In the demo output above, the actual output of the function is preceded for each write by other information which it does not write. Each displayed character preceding each first occurring :
in any of the above sequences is actually the terminal's echo
as can be configured w/ stty echo
or -echo
. The rest is what is printed as the function's output - and it is printed, as you can see, just as soon as it is typed.

- 58,310