How can I test STDIN without blocking in Perl?

2019-04-04 17:46发布

问题:

I'm writing my first Perl app -- an AOL Instant Messenger bot that talks to an Arduino microcontroller, which in turn controls a servo that will push the power button on our sysadmin's server, which freezes randomly every 28 hours or so.

I've gotten all the hard stuff done, I'm just trying to add one last bit of code to break the main loop and log out of AIM when the user types 'quit'.

The problem is, if I try to read from STDIN in the main program loop, it blocks the process until input is entered, essentially rendering the bot inactive. I've tried testing for EOF before reading, but no dice... EOF just always returns false.

Here's below is some sample code I'm working with:

while(1) {
    $oscar->do_one_loop();

# Poll to see if any arduino data is coming in over serial port
    my $char = $port->lookfor();

# If we get data from arduino, then print it
    if ($char) {
        print "" . $char ;
    }

    # reading STDIN blocks until input is received... AAARG!
    my $a = <STDIN>;
    print $a;
    if($a eq "exit" || $a eq "quit" || $a eq 'c' || $a eq 'q') {last;}
}

print "Signing off... ";

$oscar->signoff();
print "Done\n";
print "Closing serial port... ";
$port->close() || warn "close failed";
print "Done\n";

回答1:

The Perl built-in is select(), which is a pass-through to the select() system call, but for sane people I recommend IO::Select.

Code sample:

#!/usr/bin/perl

use IO::Select;

$s = IO::Select->new();
$s->add(\*STDIN);

while (++$i) {
  print "Hiya $i!\n";
  sleep(5);
  if ($s->can_read(.5)) {
    chomp($foo = <STDIN>);
    print "Got '$foo' from STDIN\n";
  }
}


回答2:

I found that IO::Select works fine as long as STDOUT gets closed, such as when the upstream process in the pipeline exits, or input is from a file. However, if output is ongoing (such as from "tail -f") then any partial data buffered by <STDIN> will not be displayed. Instead, use the unbuffered sysread:

#!/usr/bin/perl
use IO::Select;
$s = IO::Select->new(\*STDIN);

while (++$i) {
        if ($s->can_read(2)) {
                last unless defined($foo=get_unbuf_line());
                print "Got '$foo'\n";
        }
}

sub get_unbuf_line {
        my $line="";
        while (sysread(STDIN, my $nextbyte, 1)) {
                return $line if $nextbyte eq "\n";
                $line .= $nextbyte;
        }
        return(undef);
}


标签: perl stdin