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