Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I was thinking about doing

open(*STDIN, "<", "/dev/null" );
open(my $fh, "-|", "/bin/bash", "/tmp/foo");
print for <$fh>;'

However, I would like *STDIN restored, afterward, so I tried.

{
  open(local *STDIN, "<", "/dev/null" );
  open(my $fh, "-|", "/bin/bash", "/tmp/foo");
  print for <$fh>;
}

You can try this with cat with and without the local keyword like this,

{
  # remove local and it works,
  open(local *STDIN, "<", "/dev/null" );
  open(my $fh, "-|", "/bin/cat");
  print for <$fh>;
}

Only without local will cat read from /dev/null. So what does local actually do on a bareword filehandle?

like image 859
NO WAR WITH RUSSIA Avatar asked Sep 02 '20 17:09

NO WAR WITH RUSSIA


2 Answers

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.

like image 111
ikegami Avatar answered Oct 20 '22 12:10

ikegami


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 }
like image 23
mob Avatar answered Oct 20 '22 11:10

mob