Perl FAQ 4.31: How can I read a single character from the keyboard under UNIX and DOS?

Perl FAQ 4.31

How can I read a single character from the keyboard under UNIX and DOS?

A closely related question to the no-echo question below is how to input a single character from the keyboard. Again, this is a system dependent operation. As with the previous question, you probably want to get the ReadKey extension. The following code may or may not help you. It should work on both SysV and BSD flavors of UNIX:

$BSD = -f '/vmunix';
if ($BSD) {
    system "stty cbreak /dev/tty 2>&1";
}
else {
    system "stty", '-icanon',
    system "stty", 'eol', "\001"; 
}

$key = getc(STDIN);

if ($BSD) {
    system "stty -cbreak /dev/tty 2>&1";
}
else {
    system "stty", 'icanon';
    system "stty", 'eol', '^@'; # ascii null
}
print "\n";

You could also handle the stty operations yourself for speed if you're going to be doing a lot of them. This code works to toggle cbreak and echo modes on a BSD system:

sub set_cbreak { # &set_cbreak(1) or &set_cbreak(0)
    local($on) = $_[0];
    local($sgttyb,@ary);
    require 'sys/ioctl.ph';
    $sgttyb_t   = 'C4 S' unless $sgttyb_t;  # c2ph: &sgttyb'typedef()
    ioctl(STDIN,&TIOCGETP,$sgttyb) || die "Can't ioctl TIOCGETP: $!";

    @ary = unpack($sgttyb_t,$sgttyb);
    if ($on) {
	$ary[4] |= &CBREAK;
	$ary[4] &= ~&ECHO;
    } else {
	$ary[4] &= ~&CBREAK;
	$ary[4] |= &ECHO;
    }
    $sgttyb = pack($sgttyb_t,@ary);
    ioctl(STDIN,&TIOCSETP,$sgttyb) || die "Can't ioctl TIOCSETP: $!";
}

Note that this is one of the few times you actually want to use the getc() function; it's in general way too expensive to call for normal I/O. Normally, you just use the <FILE> syntax, or perhaps the read() or sysread() functions.

For perspectives on more portable solutions, use anon ftp to retrieve the file /pub/perl/info/keypress from convex.com.

Under Perl5, with William Setzer's Curses module, you can call &Curses::cbreak() and &Curses::nocbreak() to turn cbreak mode on and off. You can then use getc() to read each character. This should work under both BSD and SVR systems. If anyone can confirm or deny (especially William), please contact the maintainers.

For DOS systems, Dan Carson <dbc@tc.fluke.COM> reports:

To put the PC in ``raw'' mode, use ioctl with some magic numbers gleaned from msdos.c (Perl source file) and Ralf Brown's interrupt list (comes across the net every so often):

    $old_ioctl = ioctl(STDIN,0,0);     # Gets device info
    $old_ioctl &= 0xff;
    ioctl(STDIN,1,$old_ioctl | 32);    # Writes it back, setting bit 5

Then to read a single character:

    sysread(STDIN,$c,1);               # Read a single character

And to put the PC back to ``cooked'' mode:

    ioctl(STDIN,1,$old_ioctl);         # Sets it back to cooked mode.

So now you have $c. If ord($c) == 0, you have a two byte code, which means you hit a special key. Read another byte with sysread(STDIN,$c,1), and that value tells you what combination it was according to this table:

# PC 2-byte keycodes = ^@ + the following:

# HEX     KEYS
# ---     ----
# 0F      SHF TAB
# 10-19   ALT QWERTYUIOP
# 1E-26   ALT ASDFGHJKL
# 2C-32   ALT ZXCVBNM
# 3B-44   F1-F10
# 47-49   HOME,UP,PgUp
# 4B      LEFT
# 4D      RIGHT
# 4F-53   END,DOWN,PgDn,Ins,Del
# 54-5D   SHF F1-F10
# 5E-67   CTR F1-F10
# 68-71   ALT F1-F10
# 73-77   CTR LEFT,RIGHT,END,PgDn,HOME
# 78-83   ALT 1234567890-=
# 84      CTR PgUp

This is all trial and error I did a long time ago, I hope I'm reading the file that worked.


Other resources at this site: