por*_*ton 2 python perl runtime-error pipe flush
考虑这个Python脚本:
for i in range(4000):
print(i)
Run Code Online (Sandbox Code Playgroud)
和这个Perl脚本:
for my $i (0..4000-1) {
print $i, "\n";
}
Run Code Online (Sandbox Code Playgroud)
python3 pipe.py | head -n3000 >/dev/null 产生错误:
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
BrokenPipeError: [Errno 32] Broken pipe
Run Code Online (Sandbox Code Playgroud)
但
perl pipe.pl | head -n3000 >/dev/null 不产生错误(在Perl v5.26.1中).
为什么Python和Perl之间存在这样的差异?如何让Perl产生类似的错误信息?
这里发生的是,在这两种情况下,您都有一个进程写入管道,其读取结束已关闭(通过head在一定数量的字节后退出).
这导致SIGPIPE信号被发送到写入过程.默认情况下,这会终止进程.如果需要,该过程可以忽略该信号,这只会使write调用失败并出现EPIPE错误.
从版本3.3开始,Python BrokenPipeError在这种情况下引发异常,因此它看起来像Python 1)SIGPIPE默认忽略而2)转换EPIPE为BrokenPipeError异常.
默认情况下,Perl不会忽略或处理信号.这意味着它会SIGPIPE在你的例子中被杀死,但因为它不是管道中的最后一个命令(就在head这里),shell只是忽略它.您可以通过不使用管道使其更加明显:
perl pipe.pl > >(head -n3000 >/dev/null)
Run Code Online (Sandbox Code Playgroud)
这段bash技巧使perl写入管道,但不作为shell管道的一部分.我现在无法测试它,但至少这将设置$?(命令退出状态)到141shell(128 +信号编号,为SIGPIPE13),它也可以报告a Broken pipe.
您可以在Perl代码中手动处理它,但是:
变体1:从信号处理程序中抛出错误
$SIG{PIPE} = sub { die "BrokenPipeError" };
Run Code Online (Sandbox Code Playgroud)变体2:忽略信号,处理写入错误
$SIG{PIPE} = 'IGNORE';
...
print $i, "\n" or die "Can't print: $!";
Run Code Online (Sandbox Code Playgroud)
请注意,在这种情况下,您必须考虑缓冲.如果你没有启用autoflush(如在STDOUT->autoflush(1))并且输出转到管道或文件,Perl将首先在内部缓冲区中收集文本(并且print调用将成功).只有当缓冲区变满时(或文件句柄关闭时,以先发生者为准)才会实际写出文本并清空缓冲区.这就是为什么close还可以报告写错误的原因.
由于读取过程(head)关闭结尾,因此引发了python异常,因此脚本SIGPIPE在下次尝试写入时会收到;看到这篇文章。这涉及Python社区中的决定,即更改默认值,以便通知用户(请参阅链接的文章)。
这在Perl中没有看到,因为它被该信号杀死(它的处置方式)而没有说什么。所以你可以覆盖
use warnings;
$| = 1;
$SIG{PIPE} = sub { die $! };
for my $i (0..4_000-1) {
print $i, "\n";
}
Run Code Online (Sandbox Code Playgroud)
(没有$| = 1I,我需要的不仅仅是5_000上述事情而已。)
或者,而不是发出warning(而不是die),以便程序继续
local $SIG{PIPE} = sub { warn "Ignoring $_[0]: $!" };
Run Code Online (Sandbox Code Playgroud)
更新 鉴于评论中提供的说明,我建议此处理程序实际上是全局的。local在特定范围内,仍可以用1 覆盖它。此外,SIGPIPE只要有警告,幸存一个a 而不是终止它就没有错。
请注意,即使没有,Perl进程的退出状态也将显示问题。echo $?在过程“完成”之后运行(被悄悄终止);我进入32系统。
为了进一步模仿Python的行为,您可以die在信号处理程序中发出a 并通过将其全部放入来处理该异常eval。