perlwarningsstdoutfilehandle

Perl's odd behavior when reassigning a filehandle variable from STDOUT to a file without undef()


When executing the following simplified code:

use strict;                                          # [01]
use warnings FATAL => 'unopened';                    # [02]
                                                     # [03]
my ($inHandle, $outHandle) = (\*STDIN, \*STDOUT);    # [04]
print $outHandle "STDOUT  1\n";                      # [05]
                                                     # [06]
# $outHandle re-assigned to outputA.txt ???          # [07]
open($outHandle, ">outputA.txt") or die ("A: $!\n"); # [08]
print $outHandle "FILE A\n";                         # [09]
print             "STDOUT? 2\n";                     # [10]
print STDOUT      "STDOUT  3\n";                     # [11]
close $outHandle;                                    # [12]
                                                     # [13]
# $outHandle is closed                               # [14]
print STDOUT      "STDOUT  4\n";                     # [15]
print             "STDOUT? 5\n";                     # [16]
print $outHandle "FILE CLOSED\n";                    # [17]
                                                     # [18]
# $outHandle re-assigned to outputA.txt ???          # [19]
open($outHandle, ">outputB.txt") or die ("B: $!\n"); # [20]
print $outHandle "FILE B\n";                         # [21]
close $outHandle;                                    # [22]

I encounter these following odd behaviors:

  1. No warning is raised when printing (line [18]) to a closed (unopened) $outputHandle (line [13]), even when use warnings FATAL => 'unopened'; is used.
  2. The output is as follows which is not what I expect.
STDOUT outputA.txt outputB.txt
STDOUT 1 FILE A FILE B
STDOUT? 2
STDOUT 3

This is the output I expect assuming line [17] is commented out and doesn't raise the warnings FATAL => 'unopened'

STDOUT outputA.txt outputB.txt
STDOUT 1 FILE A FILE B
STDOUT? 2
STDOUT 3
STDOUT 4
STDOUT? 5

As a side note:

  1. The original program outputs to STDOUT as default but switches to outputting to a file if there is a parameter passed to the program.
  2. I am using "This is perl 5, version 28, subversion 1 (v5.28.1) built for MSWin32-x64-multi-thread"

Solution

  • When the standard output stream gets redirected (reopened) to a file then there is no way to print to the console with it; what was meant to go there is now connected to that file instead. So once that was done then all other prints to STDOUT, done one way or another, wind up in the file.

    And then that filehandle gets closed; after that one cannot print to STDOUT anymore.

    So the first table is what one should expect.

    I do get a warning when printing to an unopened filehandle, so for any and all prints to STDOUT after it got closed.   Edit ... without FATAL => 'unopened' but with normal warnings enabled, that is (how I tested for this answer). However, with that warning category alone there are no warnings for printing to closed filehandles (ones that had been initialized then closed). See this page.

    Some notes:


    The file descriptor 1, for which Perl provides an opened STDOUT filehandle (really *STDOUT glob, but * may be omitted when a filehandle is expected, or as a proper reference \*STDOUT)

    Even if STDOUT hasn't been first redirected, once it is closed there is no connection to standard output stream, and there is no simple way to reopen it as it had been. (There's of course ways to put things out on the terminal.)

    In general, closing STDOUT isn't a good idea, since many parties expect it to be open. For one, once fd1 is vacated other things may get it assigned, with bizarre trouble (see this post and Perl bug #23838). What if your program forks (in some way), and children processes inherit what they cannot possibly expect? What with the libraries that may get called in the next line? Etc.

    There are better ways to manipulate STDOUT, mentioned and linked in the text.

    If you need STDOUT to be gone, at least redirect it to /dev/null (nul on Windows) instead of outright closing it.