如何在 Perl 脚本中检测管道上的文件末尾?

How can I detect end of file on a pipe in a Perl script?

提问人:David Levner 提问时间:12/18/2022 更新时间:12/20/2022 访问量:132

问:

在 Perl 脚本中,我正在运行另一个进程 (openssl) 并通过管道与它通信。我用 openssl 运行 .IPC::Run::start

$openssl_stdin_handle = Symbol::gensym();
$openssl_stdout_handle = Symbol::gensym();
$openssl_stderr_handle = Symbol::gensym();
$harness = IPC::Run::start(\@openssl_command_words, '<pipe', $openssl_stdin_handle, '>pipe', $openssl_stdout_handle, '2>pipe', $openssl_stderr_handle);
my $io_select_for_stdin = IO::Select->new($openssl_stdin_handle);
my $io_select_for_stdout = IO::Select->new($openssl_stdout_handle);
my $io_select_for_stderr = IO::Select->new($openssl_stderr_handle);

我用 和 将明文数据写入 openssl。 我用 和 从 openssl 读取加密数据。我无法写入所有明文数据,然后读取所有加密数据,因为如果有很多明文数据,openssl 标准输出的管道会填满并且 openssl 挂起。相反,我必须写一些明文,看看是否有任何加密数据要读取,如果可用,请读取它,然后回去再写一些。$io_select_for_stdin->can_write(4)syswrite$io_select_for_stdout->can_read($timeout)sysread

一旦我完成对 openssl 的明文编写,我关闭并调用 .这是代码(它位于标记为 的循环中):$openssl_stdin_handle$io_select_for_stdout->can_read(1)READ_WRITE_LOOP

$OS_ERROR = 0
if  (not $io_select_for_stdout->can_read(1))
    {
    if  ($OS_ERROR == 0) {last READ_WRITE_LOOP;}   # nothing to read -- not an error
    return "error";
    }

my $number_of_cyphertext_bytes_read = sysread($openssl_stdout_handle, $cyphertext_block, $block_size);

问题是可能会返回 false,因为 openssl 还没有准备好写入,或者因为 openssl 已经完成了写入并关闭了其标准输出。在第一种情况下,我想重试读取,在第二种情况下,我想关闭并进入程序的下一阶段。如何区分这两种情况?can_read$openssl_stdout_handle

上面的代码可以正常工作,因为我将超时设置为一秒。如果我将超时缩短到 0.01 秒,代码有时会起作用,而其他时候则不起作用。我想要更短的超时时间,但我每次都需要代码才能工作。can_read

我知道呼唤不是答案。不能混合调用 和 。有没有一种干净的方法来检测 openssl 是否关闭了我通过访问的管道的一侧?eof($openssl_stdout_handle)sysreadeof$openssl_stdout_handle

perl 管道 eof perl-ipc-run

评论

0赞 zdim 12/18/2022
可能设置了特定的误差常数()?它们列在 POSIX#ERRNO%!
0赞 David Levner 12/20/2022
我看到的是,如果没有要读取的数据或达到文件末尾,can_read方法将 $OS_ERROR 设置为零。到达文件末尾(在本例中实际上是管道末尾)不是错误。
0赞 ikegami 12/20/2022
不,这是错误的。 () 仅在系统指示发生错误时才有意义。达到 EOF 不是错误。当返回 true 时(因为句柄有要读取的数据或您已达到 EOF),它将返回 true,并且毫无意义。 不帮助您检测 EOF。$!$OS_ERRORcan_read$!$!
0赞 David Levner 12/21/2022
IO::Select 文档的状态是:“若要区分超时和错误,请在调用此方法之前设置为零,并在返回空列表后进行检查。因此,检查对于区分错误和超时很有用,但对于检测 eof 则不有用。can_read$!$!

答:

3赞 ikegami 12/18/2022 #1

要回答标题问题,请在 EOF 上返回零(不要与错误返回的零混淆)、管道或非管道。sysreadundef


至于实际问题,在我看来,您可以使用以下内容:

run \@cmd, '<', \$unencrypted, '>', \my $encrypted;

如果您不想将整个文件保存在内存中,则可以使用回调 () 而不是管道作为输出。sub { }

run \@cmd,
   '<', sub {
      # Called repeatedly until it returns `undef`.
      # Return a string of bytes to send, or `undef` to signal eof...
   },
   '>', sub {
      # Do something with the string of bytes provided as argument...
   };

评论

0赞 David Levner 12/20/2022
我相信这两种方法都有效,但我正在做更多的测试,很快就会留下更详细的评论。
0赞 David Levner 12/23/2022
我使用管道、标量和子程序和 IPC::Run 进行了测试,它们都可以工作。子例程在处理大量数据时需要的时间稍长。标量是最简单的解决方案(并使原始问题变得毫无意义),所以我会选择它们。感谢所有回复的人。