0

I am using IPC::Open3 for the suggestion given by Hans Lub here.

My issue is that the open3 call works correctly for the first time, but subsequent invocations return the warning:

Use of uninitialized value in numeric ne (!=) at /usr/lib/perl5/5.8.8/IPC/Open3.pm line 215.

The code sample I am using looks like this:

use  IPC::Open3;

my $pid;
# dup the old standard output and error 
open(OLDOUT, ">&STDOUT") or die "Can't dup STDOUT: $!\n";
open(OLDERR, ">&STDERR") or die "Can't dup STDERR: $!\n";

my $transcript_file = "transcript.temp";
# reopen stdout and stderr
open (STDOUT, "|tee -i $transcript_file") or die "Can't reopen STDOUT: $!\n";
open (STDERR, ">&STDOUT")              or die "Can't reopen STDERR: $!\n";

# print statements now write to log
print "Logging important info: blah!\n";
print STDERR "OOPS!\n";

#eval { $pid = open3("\*STDIN", "\*OLDOUT", "\*OLDERR", "ls"); }; # Tried this, but doesnt seem to help. Output does not appear on STDOUT.
eval { $pid = open3(">&STDIN", ">&OLDOUT", ">&OLDERR", "ls"); }; #This works correctly
waitpid( $pid, 0 );

eval { $pid = open3(">&STDIN", ">&OLDOUT", ">&OLDERR", "ls"); }; #First warning
waitpid( $pid, 0 );

eval { $pid = open3(">&STDIN", ">&OLDOUT", ">&OLDERR", "ls"); }; #Second warning
waitpid( $pid, 0 );

I apologize if I look to be trying to get others solve my problems, but I just can't seem to get around this, and looking inside Perl modules is beyond my current understanding.

Community
  • 1
  • 1
Abhishek
  • 77
  • 6
  • I suggest you start by updating the `IPC::Open3` module. In the current version, line 215 is a blank line. – Borodin Mar 16 '15 at 11:30
  • Try `open3(\*STDIN, \*OLDOUT, \*OLDERR)`. – nwellnhof Mar 16 '15 at 11:41
  • Unfortunately, updating the module is beyond my sphere of influence. I am forced to use what is available: Perl version 5.8.8, and Open3 V1.1. – Abhishek Mar 16 '15 at 11:41
  • @nwehhnhof, tried replacing all 3 eval open3's with your suggestion, but now the open3 output does not appear on transcript (intended) as well as STDOUT/STDERR (Unintended). – Abhishek Mar 16 '15 at 11:46
  • If this is a version issue, I guess I should delete this question altogether, since it addresses an "old problem" that's already fixed and does not help anybody else. – Abhishek Mar 16 '15 at 11:48
  • 2
    If updating to a new version isn't an option, looking for workarounds might be. – Sobrique Mar 16 '15 at 11:52

2 Answers2

2

It doesn't make sense to give the same STDIN to multiple parallel process. open3 thus assumes the handle you tell open3 to use isn't used by anything else, so it closes it.

It looks like your children aren't using the STDIN you provide them, so you should provide a handle to /dev/null.

open(local *CHILD_STDIN, '<', '/dev/null') or die $!;
$pid = open3('<&CHILD_STDIN', '>&STDOUT', '>&STDERR', @cmd);
ikegami
  • 367,544
  • 15
  • 269
  • 518
1

I think the problem is the way open3 uses the file handles that you pass. If you use, say, >&STDOUT then the file handle is duped, the dupe is passed to the child process, and the parent's copy is closed. That means the second time you do the same thing you are duping a closed file handle, which doesn't have the effect you want.

The only way around this that I can see is to dupe the file handles separately and pass the dupes to the child process. It won't matter that the parent's copy of the dupes is closed because it still has the original STDOUT etc. Unfortunately it adds another three statements to each open3 call, so you woul probably want to wrap the whole thing in a subroutine, like this.

my_open3('ls');
my_open3('ls');
my_open3('ls');

sub my_open3 {

  my @cmd = @_;
  my $pid;

  open IN_COPY,  '<&', STDIN  or die "Couldn't dup STDIN: $!";
  open OUT_COPY, '>&', STDOUT or die "Couldn't dup STDOUT: $!";
  open ERR_COPY, '>&', STDERR or die "Couldn't dup STDERR: $!";

  eval {
    $pid = open3('>&IN_COPY', '>&OUT_COPY', '>&ERR_COPY', @cmd);
  };

  waitpid $pid, 0;
}

This isn't the nicest of solutions, so if anyone can see anything better then please chime in. The only alternative I can see is to let the parent keep its own standard IO handles and use completely new ones to communicate with the child process each time. Then the parent would have mess with IO::Select to do the copying from the child output to its own STDOUT and STDERR.

As nwellnhof says, if the child doesn't use its STDIN (as is the case with the ls command) then you can just pass undef as the first parameter. That saves duplicating one of three standard handles.

Borodin
  • 126,100
  • 9
  • 70
  • 144
  • The first call to `open3` will consume all input on *stdin* anyway. So for subsequent invocations of `open3`, you can simply pass `undef` as first argument. (Or pass `undef` all the time if the called program doesn't read from *stdin*.) – nwellnhof Mar 16 '15 at 11:59
  • @nwellnhof: Thanks. I've added a note on my solution. – Borodin Mar 16 '15 at 12:11
  • Thanks to both of you. This helped out. Experimenting further showed that duping STDOUT and STDERR is not needed (Don't know why, as it should logically). Since there was no need for STDIN which I was using earlier, passing undef makes the error go away. – Abhishek Mar 16 '15 at 12:25
  • @Abhishek: I've been unable to test this. Does it work as I've written it? If you describe your experience then it will be useful to others who come here looking for help with a similar problem – Borodin Mar 16 '15 at 12:28
  • Umm.... This will actually not work as is. The problem is that at every call to the subroutine, IN_COPY, OUT_COPY and ERR_COPY are updated with the ones applicable at that moment (Redirected copies!). Therefore, anything printed inside open3 will appear both in the transcript file and the STDOUT/ERR. – Abhishek Mar 16 '15 at 12:47
  • A small change needed is to copy the standard inputs/outputs before the start of logging. Updating the answer to reflect this. – Abhishek Mar 16 '15 at 12:57
  • `OUT_COPY` and `ERR_COPY` are written to by the child process but read from the parent process. They should be input handles. – mob Mar 16 '15 at 13:39
  • @mob: They're not read by the parent process. The parent has duped its own `STDOUT` and passed it to the child, so the child is sending output directly without intervention. – Borodin Mar 16 '15 at 13:48
  • Well, the (logical) corrections I did to the code were rejected... The above code will not work correctly per my last comment. Hope future readers understand the changes from my comment. -Abhishek – Abhishek Mar 16 '15 at 16:55
  • 1
    1) Only STDIN is closed. You don'y need to copy STDOUT and STDERR. 2) Giving the same STDIN to multiple processes makes no sense, so you should probably open `/dev/null` instead of duping STDIN. 3) The first arg of `open3` should be `'<&...'` rather than `'>&...'`. 4) You call `waitpid` even when `open3` fails to launch a process. – ikegami Mar 16 '15 at 18:24