The problem of the missing \n can be avoided by not using <> to do your reading. Instead, let's make a little function that does the reading for us, and supply it with a timeout so that it knows when it should return, and not simply wait for the \n forever.
use IO::Select;
{
# Actually, I should have one buffer per filehandle, but
# for simplicity, I'll assume you only ever call
# this function with one filehandle.
my $buffer = "";
# Note that timeout is for waiting for the \n after the
# first character arrives. This function will block
# forever waiting for the first character of the line
sub readWithTimeout
{
my ($inputh, $timeout) = @_;
if (length($buffer)==0) {
sysread $inputh,$buffer,500;
if (length($buffer)==0) {return undef;}
}
if ($buffer =~ s{^(.*\n)}{}) {return $1;}
my $s = IO::Select->new();
$s->add($inputh);
my @ready = $s->can_read($timeout);
while (@ready)
{
last unless
sysread $inputh,$buffer,500,length($buffer);
if ($buffer =~ s{^(.*\n)}{}) {return $1;}
@ready = $s->can_read($timeout);
}
$buffer =~ s/.+// or return undef;
return $&;
}
}
Not too ugly, really, and you can call this function instead of doing <$input>.
But this being perl, there's another way to do it - and this is even reuseable. "Just" create a descendant of Tie::Handle that wraps an existing filehandle to provide this kind of timeout:
package TimeOutHandle;
use Tie::Handle;
use IO::Select;
@ISA = qw(Tie::Handle);
sub TIEHANDLE
{
my ($class, $wrappedfh, $timeout) = @_;
my ($buffer) = '';
$timeout ||= 5; # default five second timeout
return bless [$wrappedfh, $timeout, \$buffer];
}
sub READLINE
{
my $self = shift;
my ($inputh, $timeout, $bufref) = @$self;
if (length($$bufref)==0) {
sysread $inputh,$$bufref,500;
if (length($$bufref)==0) {return undef;}
}
if ($$bufref =~ s{^.*\n}{}) {return $&;}
my $s = IO::Select->new();
$s->add($inputh);
my @ready = $s->can_read($timeout);
while (@ready)
{
last unless
sysread $inputh,$$bufref,500,length($$bufref);
if ($$bufref =~ s{^.*\n}{}) {return $&;}
@ready = $s->can_read($timeout);
}
$$bufref =~ s/.+// or return undef;
return $&;
}
package main;
# Test code for the above.
# feed me some data slowly
sub slowdata()
{
print "a"; sleep 3; print "b"; sleep 6;
print "c\nd"; sleep 2; print "e";
}
# given a filehandle, tell me what <> does.
# Also, give me when <> does it.
sub reportlines
{
my $fh = shift;
printf "%02d: __BEGIN__\n", time() % 100;
while (<$fh>)
{ s/\n/\\n/s; printf "%02d: '$_'\n", time() % 100; }
printf "%02d: __END__\n", time() % 100;
}
$|=1;
print "Regular filehandle:\n";
open (INDATA, '-|') || do {slowdata();exit();};
reportlines(*INDATA);
print "\n5 second timeout:\n";
open (INDATA, '-|') || do {slowdata();exit();};
tie *TIMEOUT5, 'TimeOutHandle', \*INDATA;
reportlines(*TIMEOUT5);
print "\n1 second timeout:\n";
open (INDATA, '-|') || do {slowdata();exit();};
tie *TIMEOUT1, 'TimeOutHandle', \*INDATA, 1;
reportlines(*TIMEOUT1);
print "\n10 second timeout:\n";
open (INDATA, '-|') || do {slowdata();exit();};
tie *TIMEOUT10, 'TimeOutHandle', \*INDATA, 10;
reportlines(*TIMEOUT10);
This code produces:
Regular filehandle:
19: __BEGIN__
28: 'abc\n'
30: 'de'
30: __END__
5 second timeout:
30: __BEGIN__
38: 'ab'
39: 'c\n'
41: 'de'
41: __END__
1 second timeout:
41: __BEGIN__
42: 'a'
45: 'b'
50: 'c\n'
51: 'd'
52: 'e'
52: __END__
10 second timeout:
52: __BEGIN__
61: 'abc\n'
63: 'de'
63: __END__
Note that the 10-second timeout doesn't impose any speed penalties on top of the ordinary handle. Also note that the 1 second timeout still waited until there was something to report. (Thus your while(<$fh>) loop won't end early by having an empty string reported)
Re-implementing this as a subclass of IO::Handle instead of a tied filehash is left as an exercise for the reader. |