Dirk Stöcker
Dirk Stöcker

Reputation: 1638

Perl file handle with scalar target and IPC::Open3

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

Answers (1)

ikegami
ikegami

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

Related Questions