2

How do I redirect STDERR to the same process file handle as the one I select()ed STDOUT to?[1]

  1. This successfully redirects STDERR to STDOUT (both lines getting printed to STDOUT):

    #!/usr/bin/perl
    
    open( STDERR, '>&', STDOUT );
    
    print "Output to STDOUT\n";
    print STDERR "Output to STDERR (redirected to STDOUT)\n";
    
  2. This successfully pipes STDOUT to a pager (less in this case), while STDERR still gets written directly to the terminal. (You see STDOUT first, paged, and once you exit the pager, you see the STDERR line.)

    #!/usr/bin/perl
    
    open( my $pager, '|-', 'less' );
    select( $pager );
    
    print "Output to STDOUT (redirected to 'less')\n";
    print STDERR "Output to STDERR\n";
    

I would like to combine the two -- have STDERR piped to the same pager as STDOUT.

  1. This does not work, I get the same results as in 2). (Funnily enough, if I pipe the script output to less again -- as in ./testme.pl | less -- the outputs are combined on STDOUT. But STDERR is not put through the pager process opened by the script itself.)

    #!/usr/bin/perl
    
    open( my $pager, '|-', 'less' );
    select( $pager );
    
    open( STDERR, '>&', STDOUT );
    
    print "Output to STDOUT\n";
    print STDERR "Output to STDERR (gets redirected to STDOUT but not to 'less')\n";
    
  2. This does not work, STDERR output vanishes completely:

    #!/usr/bin/perl
    
    open( my $pager, '|-', 'less' );
    select( $pager );
    
    open( STDERR, '>', $pager );
    
    print "Output to STDOUT\n";
    print STDERR "Output to STDERR (but does not show up anywhere)\n";
    

How do I redirect STDERR to the same process file handle as the one I select()ed STDOUT to?[1]


[1]: In the actual program, I am checking that STDOUT / STDERR are not redirected by the caller already. I left those checks out of the examples for brevity.

Should select() be the problem here, I'd be happy with achieving the same effect -- STDOUT and STDERR piped to the same pager process -- without select().

Target platforms include Git Bash for Windows, which severely restricts the availability of additional modules. An ideal answer would work without useing e.g. IPC::Open.

DevSolar
  • 67,862
  • 21
  • 134
  • 209

1 Answers1

3

How do I redirect STDERR to the same process file handle as the one I select()ed STDOUT to?

The following works for me:

use v5.38;
my $pid = open( my $pager, '|-', 'less' ) or die "Can't start less: $!";
{
    local *STDOUT = $pager;
    local *STDERR = $pager;
    #select( $pager );

    print "Output to STDOUT\n";
    print STDERR "Output to STDERR\n";
}
close $pager;

Comments to your code

  • Case #3: open( STDERR, '>&', STDOUT ); this duplicates STDOUT into STDERR, so it could have worked but for some reason it does not work together with select

  • Case #4: open( STDOUT, '>', $pager ); This opens STDOUT in write mode to a file name given by $pager. So this does not duplicate any descriptor

Håkon Hægland
  • 39,012
  • 21
  • 81
  • 174
  • Sorry, a couple of (s/STDOUT/STDERR/) typo's in there. Fixed them in the question. (#1 / #4) – DevSolar Aug 29 '23 at 10:04
  • Works quite nicely for me. Any particular reason for the `use v5.38;`? My script is working from `v5.20` upward, and your construct seems to work just as well for that version too... – DevSolar Aug 29 '23 at 10:07
  • Re "*for some reason it does not work together with select*", Cause `select` doesn't change `STDOUT`. (It changes the default handle for print/printf/say.) You need `'>&', select()` which is equivalent to `'>&', $pager` – ikegami Aug 29 '23 at 13:36
  • @DevSolar, Re "*Any particular reason for the `use v5.38;`?*", This code only requires 5.6. `use v5.38;` provides `use strict; use warnings;`, though. – ikegami Aug 29 '23 at 13:39