What does local do on the standard streams / bareword word filehandles: STDIN?

open ...,'-|' and system calls that read from standard input will try to read from file descriptor 0. If you don't mess that up, the external programs will read from the same input stream as perl's standard input.

# reads from standard input
open my $fh, '-|', "/bin/cat";
while (<fh>) { print }

# reads from /tmp/foo
open STDIN, "<", "/tmp/foo";       # replaces fd0 with handle to /tmp/foo
open my $fh, '-|', "/bin/cat";
while (<$fh>) { print }

# reads from standard input
open local *STDIN, "<", "/tmp/foo";  # doesn't close fd0, creates new fd
open my $fh, '-|',  "/bin/cat";
while (<$fh>) { print }

# reads from /tmp/foo
close STDIN;
open FOO, "<", "/tmp/foo";       # fileno(FOO) should be 0 now
open my $fh, '-|', "/bin/cat";
while (<$fh>) { print }

There are system files handles (called file descriptors, or "fd"), and there are Perl file handles. Perl file handles usually wrap a system file handles, but this isn't always the case. For example, open(my $fh, '<', \$buf) creates a Perl handle that isn't associated with any system file handle.

Other processes don't know anything about your process's variables, so they don't know anything about Perl file handles. Whatever handle is opened as fd 0 will be used as STDIN, fd 1 as STDOUT, and fd 2 as STDERR.[1]

When open is passed an existing Perl handle, this handle will be closed. If creating a new system file handle, it will be either be given the same number as the original fd (if the original handle had one) or the lowest available number (if it didn't).[2]

So, when you use open(*STDIN, ...), the new handle associated with *STDIN{IO} will also be fd 0.

$ perl -e'
   CORE::say fileno(*STDIN);
   open(*STDIN, "<", "/dev/null") or die $!;
   CORE::say fileno(*STDIN);
'
0
0

cat, reading from fd 0, will therefore notice the change.

local *STDIN creates a backup of the glob and associated *STDIN with a fresh glob. The original *STDIN is still in memory, so no resources associated with *STDIN are freed. This means that any file handle associated with *STDIN is still open.

When you use open(local *STDIN, ...), the new fd will have the lowest number available. fd 0 is still being used by the original *STDIN somewhere in memory, so fd 0 is not available. Maybe fd 3 will be the first available fd this time (1 and 2 being used by STDOUT and STDERR).

$ perl -e'
   CORE::say fileno(*STDIN);
   {  
      open(local *STDIN, "<", "/dev/null") or die $!;
      CORE::say fileno(*STDIN);
   }
   CORE::say fileno(*STDIN);
'
0
3
0

cat, reading from fd 0, will read from the original handle.

Perl could make it so whatever handles are associated with STDIN, STDOUT and STDERR become fd 0, 1 and 2 before executing cat, but it doesn't. This is left to you.


  1. While I'm describing how things work in unixy systems, things work similarly in Windows.

  2. In the situation where open is passed a handle that wraps a system file handle and a new system file handle is also being created, the internal mechanism used on unixy system is the following:

    1. A new fd is created using open. It will have the lowest available number.
    2. dup2 is used to create a duplicate of the new fd with the same number as the original fd.
    3. The fd created by open is closed.

    This means the original fd isn't closed if an error occurs.