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?
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.
While I'm describing how things work in unixy systems, things work similarly in Windows.
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:
open
. It will have the lowest available number.dup2
is used to create a duplicate of the new fd with the same number as the original fd.open
is closed.This means the original fd isn't closed if an error occurs.
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 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With