Reputation: 1638
My task is to start a program from perl and keep control flow to the starting task as well as capturing the output of the program in scalar variables. The script should only use perl modules available in perl base package.
My first approach was
use POSIX;
use IPC::Open3;
use strict;
my ($invar, $outvar, $errvar, $in, $out, $err, $pid, $pidr);
open($in, "<",\$invar);
open($out, ">",\$outvar);
open($err, ">",\$errvar);
my $cmd = "sleep 5; echo Test; sleep 5; echo Test; sleep 5;";
$pid = open3($in, $out, $err, $cmd);
my $num = 0;
for($pidr = $pid; $pidr >= 0;)
{
sleep(1) if ($pidr = waitpid($pid, WNOHANG)) >= 0;
print "$num: $outvar" if $outvar;
++$num;
}
close($in);
close($out);
close($err);
When started nothing happens. The output of the started program does not go into $outvar
. To test if my basic idea fails I tried this:
my $outvar = "";
my $out;
open($out, ">", \$outvar);
print $out "Test\n";
print "--1--\n$outvar";
print $out "Test2\n";
print "--2--\n$outvar";
which correctly outputs as expected:
--1--
Test
--2--
Test
Test2
The question is: Why does the above program not work as the test example and output the text which should be in $outvar
?
A working solution is much more complex (when you add all the security checks left out for this example):
use POSIX;
use IPC::Open3;
use strict;
my ($invar, $outvar, $errvar, $in, $out, $err, $pid, $pidr);
open($in, "<",\$invar);
open($out, ">",\$outvar);
open($err, ">",\$errvar);
my $cmd = "sleep 5; echo Test; sleep 5; echo Test; sleep 5;";
$pid = open3($in, $out, $err, $cmd);
my $num = 0;
for($pidr = $pid; $pidr >= 0;)
{
sleep(1) if ($pidr = waitpid($pid, WNOHANG)) >= 0;
my $obits; vec($obits, fileno($out), 1) = 1;
if(select($obits, undef, undef, 0) > 0)
{
my $buffer;
sysread($out, $buffer, 10240);
print "$num: $buffer" if $buffer;
}
++$num;
}
close($in);
close($out);
close($err);
It correctly prints (as should do the first one as well in similar way):
5: Test
10: Test
For the examples I removed much of the error handling and the similar code for STDERR.
Upvotes: 1
Views: 161
Reputation: 385916
open($out, ">", \$outvar);
does not create a system file handle. You'll notice that fileno($out)
is -1
. One process can't write to another's memory, much less manipulate its variables, so the whole concept is flawed. Use IPC::Run3 or IPC::Run; they effectively implement the select
loop for you. open3
is far too low-level for most applications.
The second snippet works because open3
is behaving as if $in
, $out
and $err
are unopened handles. You might as well have done
$in = gensym();
$out = gensym();
$err = gensym();
But again, you should be using IPC::Run3 or IPC::Run. Your hand-rolled version suffers from some bugs, including one that can lead to a deadlock.
Upvotes: 1