5

I want to supress output in child process and read only stderr. perlfaq8 advises to do following:

# To capture a program's STDERR, but discard its STDOUT:
use IPC::Open3;
use File::Spec;
use Symbol qw(gensym);
open(NULL, ">", File::Spec->devnull);
my $pid = open3(gensym, ">&NULL", \*PH, "cmd");
while( <PH> ) { }
waitpid($pid, 0);

But then perlcritic argues on using bareword file handles.

The only thing i can devise is to select newly opened descriptor to /dev/null instead on STDOUT, like this:

# To capture a program's STDERR, but discard its STDOUT:
use IPC::Open3;
use File::Spec;
use Symbol qw(gensym);
open my $null, ">", File::Spec->devnull;
my $old_stdout = select( $null );
my $pid = open3(gensym, ">&STDOUT", \*PH, "cmd");
select( $old_stdout );
while( <PH> ) { }
waitpid($pid, 0);

But then perlcritic doesn't like using of select. Is there more elegant solution?

Andrey Starodubtsev
  • 5,139
  • 3
  • 32
  • 46

3 Answers3

3

The minimal change is just to make the use of NULL in open no longer a bareword by changing it to *NULL.

It's still usually considered poor form to use handles of this form (Because they are global variables, though you can make them somewhat less global by applying local to them). So I would recommend changing it to instead use my variables for all the handles. It also looks like you're throwing away the stdin filehandle, so that too can be passed the null filehandle (note I'm opening it in read-write mode)

use strict;
use warnings;

use IPC::Open3;
use File::Spec;
use Symbol qw(gensym);

open(my $null, '+>', File::Spec->devnull);

my $childErr = gensym;

my $pid = open3($null, $null, $childErr, "cmd");

while(<$childErr>) { }
waitpid($pid, 0);
Tim Tom
  • 779
  • 3
  • 6
  • Re "It's still usually considered poor form to use handles of this form", except IPC::Open3 is an old module designed around their use and doesn't work as well with lexical file handles. – ikegami Dec 05 '13 at 15:05
  • Very true, I'm pretty sure I've never had any need to use gensym in the last 10 years. Also, my open should have used '+<' (no difference in this case, but usually what you want since it doesn't truncate). – Tim Tom Dec 05 '13 at 15:14
3
  • Your select doesn't actually do anything! select doesn't change STDOUT.
  • Passing a closed file handle to a program's STDIN can cause problems.

Fix:

use File::Spec qw( );
use IPC::Open3 qw( open3 );

my $child_stderr;
my $pid = do {
   open(local *CHILD_STDIN,  '<', File::Spec->devnull) or die $!;
   open(local *CHILD_STDOUT, '>', File::Spec->devnull) or die $!;
   $child_stderr = \( local *CHILD_STDERR );
   open3('<&CHILD_STDIN', '>&CHILD_STDOUT', $child_stderr, $cmd)
};
while (<$child_stderr>) { }
waitpid($pid, 0);

Notes:

  1. I don't use pass opened file handles to open3 except via the '<&SYM' and '>&SYM' mechanism. There's at least one spot where there's an issue if you don't.

  2. There are higher-level modules that are easier to use such as IPC::Run3 and IPC::Run.

  3. Using File::Spec->devnull() instead of '/dev/null' is probably overkill. Will your program actually work on other platforms without /dev/null?

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • `perlcritic` still argues about using typeglobs, it deems that STDIN, STDOUT and STDERR are the only bareword filehandles allowed. Both `IPC::Run3` and `IPC::Run` accept filehandles, so it solves the problem, thanks. I can't avoid using `devnull` - this tiny utility script must be used on both Windows and *nix, and Win use `NUL` instead of `/dev/null`. – Andrey Starodubtsev Dec 06 '13 at 15:53
  • @darkmist, Of course it does. You shouldn't use globs. But you have to with `open3`. I'm disappointed you accepted the answer with the problems I specifically said you should avoid. – ikegami Dec 06 '13 at 20:26
  • (btw `\( local *CHILD_STDERR )` and `gensym` are the same thing. Feel free to use the one of your choice. I just didn't see the point of loading the module for nothing.) – ikegami Dec 06 '13 at 20:30
  • Re: "Ah, so you dynamically switch between using Win32::ShellQuote and String::ShellQuote to build $cmd?" Why should I do this? `open3` accepts cmd and args separately, like `system` does. – Andrey Starodubtsev Dec 08 '13 at 18:06
2

The >&... expression can also include a numeric file descriptor, so

open my $NULL, '>', ... ;
my $pid = open3(gensym, ">&" . fileno($NULL), \*PH, "cmd");

is the lexical filehandle equivalent of

open NULL, '>', ... ;
my $pid = open3(gensym, ">&NULL", \*PH, "cmd");
mob
  • 117,087
  • 18
  • 149
  • 283