简体   繁体   中英

STDOUT redirected to variable not catching pipe output

I want to temporarily redirect stdout to an in memory variable. Prints are correctly redirected to my variable but not the output of a pipe (bc in my example). What is going on?

#!/usr/bin/perl

my $stdout_sink;
open(my $orig_stdout, ">&STDOUT") || die $!;
close STDOUT;
open(STDOUT, ">", \$stdout_sink) || die $!;

# produce some output in different ways
print "before bc\n"; # ok
open my $fh, "| bc";
print $fh "2+2\n";   # not ok
close $fh;

close STDOUT;  
open(STDOUT, ">&", $orig_stdout) || die $!;
print "$stdout_sink";

Actual ouput will be:

before bc

Expected output:

before bc
4

You have a detailed explanation in mob's answer of why you cannot redirect a child's STDOUT to a variable, which isn't really a filehandle.

Instead, you can use a module for running external programs, that can redirect standard streams to variables. Then you can combine strings with redirected output as you wish.

An example with IPC::Run3

use warnings;
use strict;
use feature 'say';

use IPC::Run3;

open my $fh, ">", \my $so_1;     
my $old = select $fh;         # make $fh the default for output,
say "To select-ed default";   # so prints end up in $so_1

run3 ["ls", "-l", "./"], undef, \my $so_2;   # output goes to $so_2

select $old;                  # restore STDOUT as default for output
print $so_1, $so_2;

Here I used select to manipulate where prints go by default (without a filehandle specified).

Note that the example redirects run3 to a different variable ( $so_2 ) than the one used for a previous redirect. If you'd rather append to the same variable specify this in %options

run3 ["ls", "-l", "./"], undef, \$so_1, { append_stdout => 1 };

and remove $so_2 from the printing statement.

The module uses temporary files for this redirection, as mob also indicated in the answer.

Some other options are Capture::Tiny that can redirect output from nearly any code, with a simple and clean interface, and the very powerfull, rounded, and more complex IPC::Run .

This is ... not possible.

Standard output of piped opens and system calls are written to file descriptor 1. Normally, Perl's STDOUT file handle is associated with file descriptor 1, but that can be manipulated.

In this example, the system calls writes to STDOUT filehandle, which writes to the file foo .

close STDOUT;             # closes file descriptor 1
open STDOUT, '>', 'foo';  # reopens STDOUT as file descriptor 1
system("echo bar");
close STDOUT;
print STDERR "foo: ",`cat foo`;
# result:  "foo: bar"

But in this example, the system calls writes to the BAZ filehandle.

close STDOUT;             # closes file descriptor 1
open BAZ, '>', 'baz';     # attaches fd 1 to handle BAZ
open STDOUT, '>', 'foo';  # opens new file descriptor, maybe fd 3
system("echo bar");
close STDOUT;
print STDERR "foo: ",`cat foo`;
print STDERR "baz: ",`cat baz`;
# result:  "foo: baz: bar"

An in-memory filehandle is not a real filehandle. If you call fileno on it, you will (generally, may be OS dependent) get a negative number.

open STDOUT, '>', \$scalar;
print STDERR fileno(STDOUT);     #   -1

Piped opens and system calls will not be able to write to this filehandle.

You will need a more complicated workaround, like writing the piped open output to a file, and then copying that file into the in-memory variable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM