Search code examples
perlio-redirection

Redirecting STDERR to select()ed STDOUT


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.


Solution

  • 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