In Perl (using version 5.30.0), is it possible to programmatically (i.e. within my Perl code) change some setting that would cause STDOUT and STDERR to be redirected to a text file for any child processes that my Perl process will start from that point on? The difficulty here is that I do not control the code that actually starts those child processes (if I did, this would be extremely trivial). My hope is that there exists some kind of IO flag that I can set in my Perl process that would cause all newly started child processes to redirect their STDOUT / STDERR channels to a destination of my choice (unless the system() / exec() / whatever call used to start them explicitly supplies another STDOUT / STDERR destination).
The (highly simplified) use case I need this for is the following. I have written a generic library function that performs a task specified by the caller and, among other things, stores all STDOUT / STDERR output generated by that user-provided task in a text file. The task is provided in the shape of a Perl function reference. So far I have merely managed to redirect the STDOUT / STDERR output generated by my Perl process:
local *STDOUT;
local *STDERR;
open( STDOUT, '>', $buildLogFilePath ) or die sprintf(
"ERROR: [%s] Failed to redirect STDOUT to \"%s\"!\n",
$messageSubject,
$buildLogFilePath
);
open ( STDERR, '>&', STDOUT ) or die sprintf(
"ERROR: [%s] Failed to redirect STDERR to \"%s\"!\n",
$messageSubject,
$buildLogFilePath
);
So as long as the user-provided function doesn't start any child processes, my feature works as desired, but the moment it does start a child process, that process's output will just go to the default STDOUT / STDERR channels, effectively breaking my feature.
EDIT: I ended up using the STDOUT / STDERR repointing trick described by ikegami below. To allow easier re-use of this trick, I wrapped the code in a little utility class that redirects STDOUT / STDERR in the constructor and then restores them to their original values in the destructor:
package TemporaryOutputRedirector;
use strict;
use warnings FATAL => 'all';
use Carp::Assert;
sub new
{
my ( $class, $newDest ) = @_;
open( my $savedStdout, '>&', STDOUT ) or die sprintf(
"ERROR: Failed to save original STDOUT in process %i!\n",
$$
);
open( STDOUT, '>', $newDest ) or die sprintf(
"ERROR: Failed to redirect STDOUT to \"%s\" in child process %i!\n",
$newDest,
$$
);
open( my $savedStderr, '>&', STDERR ) or die sprintf(
"ERROR: Failed to save original STDERR in process %i!\n",
$$
);
open ( STDERR, '>&', STDOUT ) or die sprintf(
"ERROR: Failed to redirect STDERR in child process %i!\n",
$$
);
my %memberData = (
'newDest' => $newDest,
'savedStdout' => $savedStdout,
'savedStderr' => $savedStderr
);
return bless \%memberData, ref $class || $class;
}
sub DESTROY
{
my ( $self ) = @_;
my $savedStdout = $$self{ 'savedStdout' };
assert( defined( $savedStdout ) );
open( STDOUT, '>&', $savedStdout ) or warn sprintf(
"ERROR: Failed to restore original STDOUT in process %i!\n",
$$
);
my $savedStderr = $$self{ 'savedStderr' };
assert( defined( $savedStderr ) );
open( STDERR, '>&', $savedStderr ) or warn sprintf(
"ERROR: Failed to restore original STDERR in process %i!\n",
$$
);
}
1;
This way, one only needs to create an instance of TemporaryOutputRedirector
, execute the code that should have its output redirected, and then let the TemporaryOutputRedirector
fall out of scope and have its destructor restore the original STDOUT / STDERR channels.
local *STDOUT; local *STDERR;
- ikegami