Perl 在打开 gzip 文件时给出“gzip:stdout:Broken pipe”错误,但前提是连接到数据库
Posted
技术标签:
【中文标题】Perl 在打开 gzip 文件时给出“gzip:stdout:Broken pipe”错误,但前提是连接到数据库【英文标题】:Perl gives "gzip: stdout: Broken pipe" error when opening gzipped files, but only if connecting to a DB 【发布时间】:2019-12-28 17:38:33 【问题描述】:考虑以下在 Linux 机器上运行的程序,它会打开一个 gzip 压缩的输入文件:
#!/usr/bin/env perl
open (my $fileHandle, "-|", "/bin/zcat $ARGV[0]");
my $ff = <$fileHandle>;
close($fileHandle);
按预期工作(它什么也不做,但不打印错误):
$ bar.pl file.gz
$
现在,如果我使用相同的代码但之前连接到 mysql 数据库,gzip 会报错(您可以直接运行代码,这是一个开放的数据库,并且凭据可以工作):
#!/usr/bin/env perl
use DBI;
use strict;
use warnings;
my $dsn = "DBI:mysql:database=hg19;host=genome-mysql.cse.ucsc.edu";
my $db = DBI->connect($dsn, 'genomep', 'password');
my $dbResults = $db->prepare("show tables");
my $ret = $dbResults->execute();
$dbResults->finish();
$db->disconnect();
open (my $fileHandle, "-|", "/bin/zcat $ARGV[0]");
my $ff = <$fileHandle>;
close($ff);
运行上面给出:
$ foo.pl file.gz
gzip: stdout: Broken pipe
这显然是一个更复杂的程序的一部分,但我已设法将其精简为重现该问题的愚蠢的 sn-p。
发生了什么事?为什么连接到数据库会影响 gzip 的行为?请注意,一切似乎都正常(在实际程序中,我对 gzip 压缩的数据做了一些有用的事情),但为什么我会收到该错误消息?
事实证明,这种行为特定于(稍微)旧版本的 Perl 和/或 DBI。在失败的机器上,我有:
Ubuntu 为 x86_64-linux-gnu-thread-multi 构建的 Perl 5,版本 22,subversion 1 (v5.22.1) DBI 1.634 DBD 4.033 gzip 1.6但是,在另外两台机器上它确实有效。这些有:
Ubuntu 为 x86_64-linux-gnu-thread-multi 构建的 Perl 5,版本 26,subversion 1 (v5.26.1) DBI 1.640 DBD 4.033 gzip 1.6和
Arch Linux 为 x86_64-linux-thread-multi 构建的 Perl 5,版本 30,subversion 0 (v5.30.0) DBI 1.642 DBD 4.050 gzip 1.10【问题讨论】:
请添加use autodie;
以检查失败的系统调用,它会显示更多信息。
两种变体对我来说都很好(在 Fedora 29 上)。
@StephenKitt 哇哦。是的,在我的 Arch 系统 (perl 5, version 30, subversion 0 (v5.30.0)
) 和带有 perl 5, version 26, subversion 1 (v5.26.1)
的 Ubuntu 系统上都可以正常工作,但在两台带有 (perl 5, version 22, subversion 1 (v5.22.1)
的 Ubuntu 机器上都失败了。所以我猜这是一个奇怪的错误,后来已经修复. 谢谢!
(可能)不相关:还请注意,您正在打开 $filehandle
,但正在关闭 $ff
。在第一段代码中,$filehandle
前面也没有my
。
不,这不是 Perl 版本。如上所述对我来说失败了。 perl 5.30.0 DBI 1.642 DBD::mysql 4.050 libmariadb-devel 3.1.2 gzip 1.10
【参考方案1】:
至少在这里,MySQL 库(可能)似乎正在屏蔽(忽略)SIGPIPE,这就是您所看到的。比较 strace 的输出,我在 MySQL 运行中看到这样一行:
rt_sigaction(SIGPIPE, sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f78bdf16840, sa_handler=SIG_DFL, sa_mask=[], sa_flags=0, 8) = 0
事实证明,您可以在不使用 MySQL 的情况下轻松复制该行为:
$SIGPIPE = 'IGNORE';
open (my $fileHandle, "-|", "/bin/zcat $ARGV[0]");
my $ff = <$fileHandle>;
close($ff);
或者,或者,您可以将信号重置为默认处理程序以使消息消失,即使在连接到 MySQL 之后,通过将其设置为 DEFAULT
而不是 IGNORE
。
顺便说一下,这是documented behavior of the MySQL library:
为避免在连接终止时中止程序,MySQL 在第一次调用 mysql_library_init()、mysql_init() 或 mysql_connect() 时阻止 SIGPIPE。
(也可能取决于 gzip 版本;可能某些版本的 gzip 在 init 上设置了信号处理程序。)
最终,您看到的是,如果 gzip 获得 SIGPIPE,它就会退出。如果它从 write 中返回错误(因为 SIGPIPE 被忽略),它会打印一条错误消息。
【讨论】:
在我的 Arch 上(有关版本,请参阅问题)即使使用$SIGPIPE = 'IGNORE';
,我也无法重现它。然而,在失败的机器上,$SIGPIPE = 'IGNORE';
确实足以重现。所以,鉴于 gzip 版本是相同的,我可以在没有 DBI 代码的情况下重现,看起来它确实是一个 perl 版本的东西。【参考方案2】:
很可能发生了以下情况: gzip 尝试写入管道,您这边的程序没有读取到 eof,关闭管道。然后 Gzip 收到一个 SIGPIPE,并死于此错误消息。你能确认这正在发生吗?
【讨论】:
但是为什么只有在使用不相关的 DBI 代码运行时才会出现问题,而没有它也能正常工作呢? 谢谢,但事实证明这是我使用的版本中的一个错误,现已修复。哦! 很高兴知道。您能否添加一些有关不良版本的信息,以免其他人对此感到疑惑?以上是关于Perl 在打开 gzip 文件时给出“gzip:stdout:Broken pipe”错误,但前提是连接到数据库的主要内容,如果未能解决你的问题,请参考以下文章
使用管道在 Perl 中将管道文件输出到 gzip 的 Python 等效项
Bash 脚本:给出错误:gzip:<文件>.conf.gz:文件意外结束