Perl 在没有 undef() 的情况下将 filehandle 变量从 STDOUT 重新赋值到文件中时的奇怪行为

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

提问人:mak 提问时间:2/25/2021 最后编辑:zdimmak 更新时间:6/18/2021 访问量:183

问:

执行以下简化代码时:

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]

我遇到以下奇怪的行为:

  1. 打印(行 [18])到关闭(未打开)(行 [13])时,即使使用也不会发出警告。$outputHandleuse warnings FATAL => 'unopened';
  2. 输出如下,这不是我所期望的。
标准输出 输出 .txt 输出B.txt
标准输出 1 文件 A 文件 B
性病?2
标准输出 3

这是我期望的输出,假设第 [17] 行被注释掉并且不会引发warnings FATAL => 'unopened'

标准输出 输出 .txt 输出B.txt
标准输出 1 文件 A 文件 B
性病?2
标准输出 3
标准输出 4
性病?5

顺便说一句:

  1. 默认情况下,原始程序输出到 STDOUT,但如果有参数传递给程序,则切换到输出到文件。
  2. 我正在使用“这是为 MSWin32-x64-multi-thread 构建的 perl 5,版本 28,subversion 1 (v5.28.1)”
Perl 警告 stdout 文件句柄

评论

3赞 zdim 2/25/2021
您问题中的那些行号......我很欣赏在那里提供它们的想法(供参考?),但拥有可以轻松复制粘贴的代码以进行测试要方便得多
1赞 ikegami 2/26/2021
我同意 zdim,希望能够运行代码。(至少它们是固定宽度的,因此在列模式下很容易移除。将来将行号作为注释包括在内(如果您希望包含它们)。
1赞 ikegami 2/26/2021
Nit:它们是手柄(它们给你一些东西),而不是处理程序(它们不处理任何东西)
0赞 ikegami 2/26/2021
如果您尝试临时重定向 STDOUT,则需要备份说明。使用 或 use 将 dup STDOUT 分配给另一个句柄,稍后再次将其 dup 重新分配给 STDOUT。更好的是,使用现有的模块之一,例如 Capture::Tinylocal *STDOUT>&
0赞 mak 2/26/2021
我以固定宽度的格式包含这些行号以供参考。但是,根据您的建议(zdim 和 ikegami 的建议),我将它们作为评论移到了最后。此外,我还更改了误导性的变量名称:从 xxxHandler 更改为 xxxHandle。感谢您的评论。

答:

0赞 mak 2/25/2021 #1

在第 [08] 行之前调用将完成输出工作。
但是,不能解决打印到第 [18] 行的已关闭(未打开)文件句柄时引发的未发出警告的问题。
undef($outHandle)undef($outHandle)

评论

0赞 mak 3/1/2021
您也可以这样调用行 [08]:myopen(my $outHandle, ">outputA.txt") or die ("A: $!\n");
0赞 mak 3/3/2021
此处提及并解决了有关无警告的问题:尝试打印到已关闭的文件句柄时没有错误或警告
6赞 zdim 2/25/2021 #2

当标准输出流被重定向(重新打开)到文件时,就无法使用它打印到控制台;本来要去那里的东西现在连接到了该文件。因此,一旦完成,所有其他打印件都会以一种或另一种方式完成,最终出现在文件中。STDOUT

然后该文件句柄被关闭;在那之后,就无法再打印了。STDOUT

因此,第一张表人们应该期待的。

打印到未打开的文件句柄时,我确实收到警告,因此对于任何和所有打印,在它关闭后。编辑 ...没有但启用了正常,这就是(我如何测试这个答案)。但是,仅使用该警告类别,就没有打印到已关闭的文件句柄(已初始化然后关闭的文件句柄)的警告。请参阅此页面STDOUTFATAL => 'unopened'warnings

一些注意事项:

  • 文档中的几页要学习:打开并使用 STDIN 和 STDOUT(旧 perlopentut),并在 perlfunc 中打开 FILEHANDLE

  • 有一些方法可以通过控制来操作标准流。一种是“复制”它,所以在它被重定向、使用和关闭后,人们可以恢复它。我想到的一些例子:在 STDOUT 和重定向的帖子中。(请注意,这会创建一个别名,因此当其中一个更改时,另一个也会更改。$fh = \*STDOUT

    或者,在一个单独的范围内(块会做得很好),做,然后所有提到的都将与这个本地副本一起使用。离开作用域后,全局作用域将恢复。local *STDOUT;STDOUT

    或者你可以使用而不是弄乱本身。selectSTDOUT

    其中大部分在 perl.com 文章中得到了很好的总结。有关更多信息,请参阅此页面

  • “三个论点”更好:(并检查openopen my $fh, '<', $file ...or die $!)

  • 它被称为“句柄”,而不是“处理程序”


文件描述符 1,Perl 为其提供了一个打开的文件句柄(实际上是 glob,但在需要文件句柄时可以省略,或者作为适当的引用STDOUT*STDOUT*\*STDOUT)

即使没有首先重定向,一旦关闭,就没有连接到标准输出流,也没有简单的方法可以像以前一样重新打开它。(当然,有一些方法可以把东西放在终端上。STDOUT

一般来说,关闭不是一个好主意,因为许多各方都希望它是开放的。首先,一旦 fd1 被腾空,其他东西可能会被分配,带来奇怪的麻烦(参见这篇文章Perl 错误 #23838)。如果你的程序(以某种方式)分叉,并且子进程继承了他们不可能期望的东西,该怎么办?下一行中可能调用的库怎么办?等。STDOUT

有更好的方法可以在文本中操纵、提及和链接。STDOUT

如果您需要离开,至少将其重定向到(在 Windows 上)而不是直接关闭它。STDOUT/dev/nullnul

评论

0赞 mak 2/26/2021
我很抱歉将 2 个奇怪的行为(奇怪的输出和没有警告)放入 1 个 stackoverflow 问题中......导致混乱。关于奇怪的输出,我现在明白它不会从 to outputA.txt 文件句柄重新分配。相反,它采用原始赋值并将 () 重定向到文件句柄。要严格地从变量中重用变量,它需要在行 [08] 之前。谢谢你让我明白。open($outHandle, ">outputA.txt")$outHandleSTDOUTSTDOUT>$outHandleSTDOUTundef($outHandle)open($outHandle, ">outputA.txt")
1赞 zdim 2/26/2021
@MakotoWada 感谢您如此谨慎地对待提问方式的常见做法。在这里,我认为给出两个问题是完全合理的,因为这两个问题都直接源于观察到的这个测试用例的行为。因此,它是一种情况,一个具有各种行为的代码片段。
0赞 zdim 2/26/2021
@MakotoWada 这是一个预定义的文件句柄,为方便起见而提供,它与标准输出流(文件描述符 1)相关联,并具有其他有用的属性。一旦它被“重定向”(更改为连接到另一个资源),就很难将其恢复到默认角色。(可以将其重新打开到 或其他合适的设备,但这只是其作用的一部分。作的词法变量 () 具有文件句柄,对变量执行的操作与句柄有关。STDOUT/dev/tty$outHandleSTDOUT
0赞 zdim 2/26/2021
@MakotoWada 因此,当我们需要操作时,我们首先复制它或“复制”它或 -ize 它,如文本中链接的文档/帖子中所述(或使用)。然后,我们稍后可以恢复它的全部角色。如果它在以某种方式(正确)“备份”之前附加到其他东西或关闭,那么出于所有实际目的,它作为与标准输出流的连接将丢失。如果对 glob 被分配到的词法变量执行,则所有这些都成立;然后这个变量代表它,代表它。STDOUTlocalselect*STDOUT
1赞 zdim 2/26/2021
@MakotoWada 非常感谢您再次提及此事!我在没有使用的情况下测试了这一点,以为这无关紧要——但它确实如此!我发布了新问题的答案,并附上了我的发现。所以单独问是完全正确的,谢谢!(我也相应地编辑了这个答案)FATAL