16

I want to redirect STDERR and STDOUT to a variable. I did this.

close(STDOUT);
close(STDERR);

my $out;
open(STDOUT, ">>", \$out);
open(STDERR, ">>", \$out);

for(1..10)
{
    print "print\n"; # this is ok. 
    warn "warn\n"; # same
    system("make"); # this is lost. neither in screen nor in variable.
}

The problem with system. I want the output of this call to be captured too.

Deck
  • 1,969
  • 4
  • 20
  • 41

6 Answers6

14

use Capture::Tiny!

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
salva
  • 9,943
  • 4
  • 29
  • 57
  • This answer could explain how to do it with core Perl, and then, maybe, refer to a package that exists to help replicating code. Just throwing in a name of a package is a bit poor kind of solution IMHO, even if it works. – U. Windl Aug 23 '23 at 08:06
6

Are you seeking to capture the output in a variable? If so, you have use backticks or qx{} with appropriate redirection. For example, you could use:

#/usr/bin/env perl
use strict;
use warnings;

# Ensure we have a way to write messages
open my $fh, '>', "output" or die;

close(STDOUT);
close(STDERR);

my $out;
open(STDOUT, ">>", \$out) or do { print $fh, "failed to open STDOUT ($!)\n"; die };
open(STDERR, ">>", \$out) or do { print $fh, "failed to open STDERR ($!)\n"; die };

foreach my $i (1..10)
{
    print "print $i\n"; 
    warn "warn $i\n";
    my $extra = qx{make pth$i 2>&1};
    print $fh "<<$i>><<$out>><<$extra>>\n";
}

(I happen to have programs pth1, pth2 and pth3 in the directory - they were made OK; pth4 and above write errors to stderr; the redirection was necessary.)

You should always check the success of operations such as open().

Why is this necessary? Because writing to a variable requires the cooperation of the process doing the writing - and make doesn't know how to cooperate.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • The problem with `qx` is that you can only capture one (STDOUT) output stream. If you want STDERR separately, you cannot redirect STDERR to STDOUT. – U. Windl Aug 23 '23 at 08:09
5

There are several ways to redirect and restore STDOUT. Some of them work with STDERR too. Here are my two favorites:

Using select:

my $out;
open my $fh, ">>", \$out;
select $fh;
print "written to the variable\n";
select STDOUT;
print "written to original STDOUT\n";

Using local:

my $out
do {
    local *STDOUT;
    open STDOUT, ">>", \$out;
    print "written to the variable\n";
};
print "written to original STDOUT\n";

Enjoy.

mndrix
  • 3,131
  • 1
  • 30
  • 23
  • It seems this answer misses the fact that redirection to a variable seems to fail for external processes being run. – U. Windl Aug 23 '23 at 08:11
2

The reason this is happening is that the STDOUT and STDERR "filehandles" are not equivalent to stderr and stdout handles provided by the shell to the perl binary. In order to achieve what you want, you should use open instead of system

salva
  • 9,943
  • 4
  • 29
  • 57
grncdr
  • 976
  • 6
  • 13
1

Why not use IPC::Open3?

Dallaylaen
  • 5,268
  • 20
  • 34
1

TLDR Answer

use Capture::Tiny;

Merged STDOUT and STDERR

If you want STDOUT (from print()s) and STDERR (from warn()s) to be merged, then use...

my ($merged,  @result) = capture_merged { print "Hello, world!" };  # static code
my ($merged,  @result) = capture_merged { eval $codetoeval };       # code in variable

Separated STDOUT and STDERR

If you want them separated...

my ($stdout, $stderr, @result) = capture { print "Hello, world!" };   # static code
my ($stdout, $stderr, @result) = capture { eval $codetoeval };        # code in variable

Results of Eval

@result indicates the success, with success being [1], and failure being []. Tiny has a ton of other functions that you can look through for other cases, like code references, etc.. But I think the code above should cover most of any Perl developer's needs.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
  • It's after https://stackoverflow.com/a/4415743/6607497, but you explained some details, so I upvoted the answer. – U. Windl Aug 23 '23 at 08:15