I think your main problems is that select does not interact well with buffered I/O, which you use with <READERR> and <READOUT>. You should really use sysread and do a split /\n/ at the very end.
Here's how I might do it, using IPC::Open3 and IO::Select. You should add the waitpid/close from your example:
#!perl -w
use IPC::Open3;
use IO::Select;
use Symbol qw(gensym);
use strict;
my $cmd = shift;
my $g_timeout = shift || 5;
my $g_maxlines = shift || 10;
my @output;
my @err;
$| = 1;
runcmd(\$cmd, \@output, \@err);
print "output = \n";
print " $_\n" for @output;
print "error = \n";
print " $_\n" for @err;
sub runcmd {
my ($cmdref, $outref, $errref) = @_;
my ($childin, $childout, $childerr);
$childerr = gensym;
my $pid = open3($childin, $childout, $childerr, $cmd)
or die "Cannot run cmd '$cmd': $!\n";
my $select = IO::Select->new or die "Cannot create select object: $!
+\n";
my @hold_output;
for (($childout, $childerr)) {
$select->add($_);
$hold_output[fileno($_)] = [0, 0, ""]; # eof, lines, buffer;
}
$@ = undef;
eval {
local $SIG{ALRM} = sub { die "alarm\n" };
my $deadline = $g_timeout + time;
my $g_stop = 0;
alarm($g_timeout + 1);
while (!$g_stop && $select->count > 0) {
$! = 0;
my @ready = $select->can_read($deadline - time);
if (!@ready) {
$g_stop = 1;
}
for my $handle (@ready) {
my $fno = fileno($handle);
my $line;
my $bytesread = sysread $handle, $line, 1024;
if ($bytesread) {
$hold_output[$fno][2] .= $line;
$hold_output[$fno][1] += $line =~ y/\n/\n/;
if ($hold_output[$fno][1] >= $g_maxlines) {
$select->remove($handle);
}
} elsif ($!) {
die "$!";
} else {
$hold_output[$fno][0] = 1; #EOF
$select->remove($handle);
}
}
}
alarm(0);
};
if ($@) {
# print STDERR "\$\@ = $@\n";
die unless $@ eq "alarm\n";
}
# Note: lines may exceed $g_maxlines because of buffering
# of output in the child process
@$outref = split /\n/, $hold_output[fileno($childout)][2];
@$errref = split /\n/, $hold_output[fileno($childerr)][2];
}