Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

y/n input in a captive interface

by apotheon (Deacon)
on Feb 07, 2007 at 22:43 UTC ( #598898=perlquestion: print w/replies, xml ) Need Help??

apotheon has asked for the wisdom of the Perl Monks concerning the following question:

I normally consider console-based captive interfaces "evil", though once in a while they're quite useful (Debian's debfoster tool comes to mind). As such, I haven't really dealt with this issue before. Yesterday, however, I found myself writing something with a captive interface as glue for a set of separate tools and utilities because I was tired of having to rethink what I'm doing every time I want to download a YouTube video (yes, really). I do so rarely enough that the series of steps I generally take isn't cemented in my mind, but commonly enough that it's still annoying to have to remind myself every time.

The wall I've run into is in answering yes or no to questions. To make this script really slick, I want to take a y/n/Enter keystroke as input, and not have to use the Enter key as a cue that input is done when entering either a y or n answer. Single-keystroke input is what I'm after.

The caveat is that I don't want to use an entire module that isn't part of the core Perl distribution, either. Thus, Term::Readkey doesn't seem to be the answer I seek. I also don't want to have to rely on system-specific shell commands, however -- I want to do it with Perl, not with bash or tcsh.

This doesn't seem like the sort of thing that should be terribly difficult, but I'm having a lot of trouble figuring out how it can be accomplished. Am I missing some global variable that, set to an empty string, would solve the endline requirement for input from STDIN? Is there a core function, or a function in a core module, that can be fit into this round hole?

Is there simply no way to do it without going outside the core Perl distribution (or reproducing a significant chunk of Term::Readkey)?

print substr("Just another Perl hacker", 0, -2);
- apotheon
CopyWrite Chad Perrin

Replies are listed 'Best First'.
Re: y/n input in a captive interface
by johngg (Canon) on Feb 07, 2007 at 23:23 UTC
    Firstly, Term::Readkey is a pretty small module so it's no great overhead to install and use it and it seemed to do everything right whenever I used it. My advice would be to go with that.

    However, if you are unable to do that you probably have to go with changing terminal settings by hand. I copied a recipe out of the 2nd edition Camel Book a long, long time ago and I haven't used it in years. It may give you a start though so here it is.

    # RawTerm.pm # # Adapted from "Programming PERL", Wall/Christiansen/Schwartz, 2nd Edn +. pp.474 # package RawTerm; use Exporter; @ISA = ('Exporter'); @EXPORT = ('getone'); 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(); } 1;

    If I remember correctly you just call the getone() subroutine to get a single character without needing the <Enter> key. I hope this is of use.

    Cheers,

    JohnGG

      Firstly, Term::Readkey is a pretty small module so it's no great overhead to install and use it and it seemed to do everything right whenever I used it. My advice would be to go with that.
      It's not much overhead for the processor or RAM, but it's a fair bit of overhead for someone who isn't a Perl programmer and is thus unlikely to be familiar with the task of installing nonstandard Perl modules. It's more end-user overhead that I am to avoid, rather than system overhead, in this case.

      As things currently stand, I have a workaround built into the thing so that it uses Term::ReadKey if it exists on the system (using an eval), but it's pretty kludgey, and it ends up having less slick, intuitive behavior for the less technically savvy user as a result -- pretty much the exact opposite of a desirable state of affairs.

      As for your example code, it unfortunately looks pretty environment-specific (POSIX::Termios makes me think so, anyway). I prefer to avoid assumptions about the operating environment as much as possible when writing stuff like this, where its usefulness is not limited to a particular OS by definition. I appreciate the attempt at helping, though it ultimately doesn't really provide what I need.

      print substr("Just another Perl hacker", 0, -2);
      - apotheon
      CopyWrite Chad Perrin

Re: y/n input in a captive interface
by liverpole (Monsignor) on Feb 07, 2007 at 23:33 UTC
    Hi apotheon,

    I'm assuming, since you mentioned bash and tcsh that you're in Linux or Unix.

    I've always used stty in conjunction with dd for this kind of thing.  For example, the following should help you get there:

    use strict; use warnings; $| = 1; print "Enter some characters below, <Escape> to quit\n"; chomp(my $stty = `stty -g`); # Save state system("stty -icrnl -icanon -echo min 0 time 0"); # Raw mode while (1) { my $k = `dd bs=1 count=1 <&0 2>/dev/null`; next unless $k; # No character entered last if (27 == ord $k); # Finish if char is <Escape> printf "Ascii = %d\n", ord($k); } system("stty $stty"); # Restore cooked mode (thanks, sgt!) print "\nDone!\n";

    It reads characters one at a time, displaying their ascii value, until you type an Escape.  Of course, you could easily modify it to read only a single character, or as many as you need.

    Good luck!

    Update:  Good catch sgt.  I've added the line system("stty $stty"); to restore "cooked" mode.


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

      just noticing that you need another stty (with input $stty) to bring back the previous terminal settings..

      cheers --stephan

      Thanks for the suggestion, but unfortunately something that requires backticks or something like system() tends to be exactly the sort of thing (other than non-core modules) I wanted to avoid. It really hurts portability.

      Your assumption about using a Unixlike OS is correct -- I'm on FreeBSD -- but I don't want to limit the usefulness of this script to OSes that include the standard unixy core utilities. It's bad enough requiring a specific language interpreter (Perl's, specifically) to run the thing.

      print substr("Just another Perl hacker", 0, -2);
      - apotheon
      CopyWrite Chad Perrin

Re: y/n input in a captive interface
by shmem (Chancellor) on Feb 08, 2007 at 12:53 UTC
    None of the answers so far seem to be to your liking. Sounds like a "wash but don't wet" challenge.

    The problem you have is the line discipline of the terminal. To read a single char from it, you must set it into cbreak mode.

    With the perl core, you can do that only with POSIX. With system utilities, use stty (afaik there's no UNIX or UNIX like or Linux out there which doesn't have that basic utility). After having set your terminal with either method, it's just sysread:

    #!/usr/bin/perl system 'stty','cbreak' and die; sysread STDIN,$a,1,0; print "\n",$a =~ /y/ ? "yup\n" : "nope\n"; system 'stty','-cbreak' and die;
    For the same with POSIX see johngg's answer. POSIX::Termios is in $Config::Config{installarchlib}/auto/POSIX.so, so it is available with every *nix perl.

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

      Does POSIX work for, say, Windows and Plan 9, too? I'm not too worried about BeOS, I guess. I'm asking because I honestly don't know.

      print substr("Just another Perl hacker", 0, -2);
      - apotheon
      CopyWrite Chad Perrin

        No - at least not within ActiveState Perl:
        POSIX::termios not implemented on this architecture at C:/Perl/site/li +b/RawTerm. pm line 15. BEGIN failed--compilation aborted at C:/Perl/site/lib/RawTerm.pm line +43.

        --shmem

        _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                      /\_¯/(q    /
        ----------------------------  \__(m.====·.(_("always off the crowd"))."·
        ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: y/n input in a captive interface
by Moron (Curate) on Feb 08, 2007 at 13:42 UTC
    You could always use the source of Term::Readkey as a reference to build what you want in whatever way does meet your criteria - assuming that's possible of course ;)

    -M

    Free your mind

      I'm sure it does, since Term::ReadKey (now that I've read more about it) seems to only use modules in the core Perl distribution and actually seems to do what I want. I just need to get over the fact that it's not nearly as easy to accomplish as it seems like it should be. I guess I'm just mystified at the fact that Perl can handle automatic translation of newline characters and (back)slashes between systems invisibly, but can't do the same for terminal interactions like this.

      print substr("Just another Perl hacker", 0, -2);
      - apotheon
      CopyWrite Chad Perrin

Re: y/n input in a captive interface
by Corion (Pope) on Feb 08, 2007 at 12:56 UTC

    I guess you won't get better than ExtUtils::MakeMaker::prompt, which doesn't do the "one-key" feature you want.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://598898]
Approved by shonorio
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (6)
As of 2021-06-24 22:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    What does the "s" stand for in "perls"? (Whence perls)












    Results (133 votes). Check out past polls.

    Notices?