tags:

views:

423

answers:

2

Using Perl, how do I capture a single character from STDIN without needing the user to hit enter (similar to C's getch() function)?

Perl has a getc() function, but according to the perlfunc:

However, it cannot be used by itself to fetch single characters without waiting for the user to hit enter.

The perlfunc docs do provides a way to read a single character using getc() but it requires manipulating the terminal settings using stty. The script I'm writing needs to work on Windows (without cygwin, msys, etc.) - so that's not an option.

+6  A: 

You want Term::ReadKey.

Jonathan Feinberg
Has it been ported on windows?
gameover
Did you *read* the linked-to page?
Jonathan Feinberg
Thanks! I was already using Term::Readkey to disable the echoing (for password input). This works great.
Kassini
+6  A: 

From perlfaq5's answer to How can I read a single character from a file? From the keyboard?


You can use the builtin getc() function for most filehandles, but it won't (easily) work on a terminal device. For STDIN, either use the Term::ReadKey module from CPAN or use the sample code in getc in perlfunc.

If your system supports the portable operating system programming interface (POSIX), you can use the following code, which you'll note turns off echo processing as well.

#!/usr/bin/perl -w
use strict;
$| = 1;
for (1..4) {
    my $got;
    print "gimme: ";
    $got = getone();
    print "--> $got\n";
    }
exit;

BEGIN {
use POSIX qw(:termios_h);

my ($term, $oterm, $echo, $noecho, $fd_stdin);

$fd_stdin = fileno(STDIN);

$term     = POSIX::Termios->new();
$term->getattr($fd_stdin);
$oterm     = $term->getlflag();

$echo     = ECHO | ECHOK | ICANON;
$noecho   = $oterm & ~$echo;

sub cbreak {
    $term->setlflag($noecho);
    $term->setcc(VTIME, 1);
    $term->setattr($fd_stdin, TCSANOW);
    }

sub cooked {
    $term->setlflag($oterm);
    $term->setcc(VTIME, 0);
    $term->setattr($fd_stdin, TCSANOW);
    }

sub getone {
    my $key = '';
    cbreak();
    sysread(STDIN, $key, 1);
    cooked();
    return $key;
    }

}

END { cooked() }

The Term::ReadKey module from CPAN may be easier to use. Recent versions include also support for non-portable systems as well.

use Term::ReadKey;
open(TTY, "</dev/tty");
print "Gimme a char: ";
ReadMode "raw";
$key = ReadKey 0, *TTY;
ReadMode "normal";
printf "\nYou said %s, char number %03d\n",
    $key, ord $key;
brian d foy
@Brain: Is Term::ReadKey available through activePerl?
gameover
This should tell you if you have it installed (I have it on ActiveState 5.10.1): perl -MTerm::ReadKey -e 1
toolic
@gameover: `ppm search Term::ReadKey` indicates it is.
Anonymous
Remember that ActivePerl is a distribution of Perl that runs on several platforms. If you are concerned about a particular platform, ask about that one. :)
brian d foy