标准流/裸字文件句柄的本地作用是什么?STDIN是什么意思?

6

我正在考虑做什么

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

然而,我希望恢复*STDIN,于是尝试如下。

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

您可以尝试使用 cat 带或不带 local 关键字,如下所示:

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

只有没有local时,cat才会从/dev/null读取。那么在裸字文件句柄上使用local到底有什么作用呢?
2个回答

4

系统文件有一个句柄(称为文件描述符或 "fd"),而Perl文件也有一个句柄。Perl文件通常包装一个系统文件,但并非总是如此。例如,open(my $fh, '<', \$buf) 创建一个与任何系统文件句柄都不相关联的 Perl 句柄。

其他进程不知道您进程的变量,因此它们不知道任何关于 Perl 文件句柄的事情。无论哪个句柄作为 fd 0 打开,都将用作标准输入(STDIN),fd 1 作为标准输出(STDOUT),fd 2 作为标准错误(STDERR).[1]

open 接收到一个现有的 Perl 句柄时,该句柄将被关闭。如果创建一个新的系统文件句柄,则它将被赋予与原始 fd 相同的编号(如果原始句柄有)或最低可用编号(如果没有)。[2]

因此,当您使用 open(*STDIN, ...) 时,与 *STDIN{IO} 关联的新句柄也将是 fd 0。

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

cat命令从文件描述符0(即标准输入)读取数据,因此会注意到变化。

local *STDIN创建了一个全局变量的备份和相关的*STDIN,并重新创建了全新的全局变量。原始的*STDIN仍然在内存中,因此没有与*STDIN关联的资源被释放。这意味着任何与*STDIN关联的文件句柄仍然是打开状态。

当您使用open(local *STDIN, ...)时,新的文件描述符将具有可用的最低号码。文件描述符0仍然被原始的*STDIN在内存中使用,因此文件描述符0不可用。也许这一次文件描述符3将是第一个可用的文件描述符(1和2已经被STDOUT和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,从fd 0读取,将从原始句柄读取。 在执行cat之前,Perl可以使与STDIN、STDOUT和STDERR关联的所有句柄成为fd 0、1和2,但它没有这样做。这留给你自己去完成。
  1. 尽管我在描述unixy系统的工作原理,但Windows的情况也类似。
  2. open传递包装系统文件句柄的句柄并且还创建了一个新的系统文件句柄的情况下,在unixy系统上使用的内部机制如下:
    1. 使用open创建一个新的fd。它会有最低可用的编号。
    2. dup2用于创建具有与原始fd相同编号的新fd的副本。
    3. open创建的fd被关闭。

    这意味着如果发生错误,原始fd不会关闭。


1
"Perl可以在执行cat命令之前将与STDIN、STDOUT和STDERR相关联的句柄变为fd 0、1和2,但它并没有这样做。这留给了你自己去处理。" 这个表述非常清晰明了地展示了误解。在我看来,这肯定会为用户界面带来更好的体验。 - Evan Carroll
你在第三段描述的完全是幻想。当你使用 open(*STDIN, "file") 时,Perl 首先会打开 "file",然后将返回的文件描述符 dup2(2) 到 fd 0 中。首先关闭与 stdin 关联的文件描述符是没有意义的,因为打开新文件可能会失败。实际上,Perl 并没有这样做,一个简单的 strace 或源代码检查就可以证明这一点。 - user10678532

4

open ...,'-|'和从标准输入读取的system调用将尝试从文件描述符0读取。如果你不弄错,外部程序将从与perl标准输入相同的输入流中读取。

# 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 }

这只是local *STDIN一个非常奇怪,难以理解且未记录的行为。我希望它会存储旧值,并在作用域结束后返回它(就像它对其他所有内容做的那样)。不过,至少你已经告诉我它在做什么了。 - Evan Carroll
2
*STDIN 是一个类型环境(在 STDIN 命名空间中保存包数据),而不仅仅是文件环境。local *STDIN 创建了一个新的类型环境,但不会影响原始的 *STDIN 命名空间中的任何内容,因此它不会影响 I/O 流文件描述符 0 所连接的内容。 - mob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接