我浏览了open3的文档,这是我无法理解的部分:
如果你试图从孩子的stdout作家和他们的stderr读取 作家,你会遇到阻塞问题,这意味着你会想要 使用select()或IO :: Select,这意味着你最好使用sysread() 而不是正常的东西的readline()。
这非常危险,因为你可能永远阻止。它假定它是 和bc这样的人交谈,既写作又读书 从中。这可能是安全的,因为你“知道”这些命令 bc将一次读取一行并一次输出一行。程式 比如,首先读取整个输入流的排序 很容易造成僵局。
所以我尝试了open3
希望更好地了解它。这是第一次尝试:
sub hung_execute {
my($cmd) = @_;
print "[COMMAND]: $cmd\n";
my $pid = open3(my $in, my $out, my $err = gensym(), $cmd);
print "[PID]: $pid\n";
waitpid($pid, 0);
if(<$err>) {
print "[ERROR] : $_" while(<$err>);
die;
}
print "[OUTPUT]: $_" while (<$out>);
}
值得注意的是,我必须在此初始化$err
。
无论如何,当execute("sort $some_file");
$some_file
是包含超过4096个字符(我的机器的限制)的文本文件时,这只会挂起。
然后我查看了this常见问题解答,下面是我执行的新版本:
sub good_execute {
my($cmd) = @_;
print "[COMMAND]: $cmd\n";
my $in = gensym();
#---------------------------------------------------
# using $in, $out doesn't work. it expects a glob?
local *OUT = IO::File->new_tmpfile;
local *ERR = IO::File->new_tmpfile;
my $pid = open3($in, ">&OUT", ">&ERR", $cmd);
print "[PID]: $pid\n";
waitpid($pid, 0);
seek $_, 0, 0 for \*OUT, \*ERR;
if(<ERR>) {
print "[ERROR] : $_" while(<ERR>);
die;
}
print "[OUTPUT]: $_" while (<OUT>);
}
sort
命令现在执行正常,但我无法弄清楚原因。
[更新] 在阅读了@ tchrist的回答后,我看了IO::Select
,经过一些谷歌搜索后,我们想出了execute
的这个版本:
sub good_execute {
my($cmd) = @_;
print "[COMMAND]: $cmd\n";
my $pid = open3(my $in, my $out, my $err = gensym(), $cmd);
print "[PID]: $pid\n";
my $sel = new IO::Select;
$sel->add($out, $err);
while(my @fhs = $sel->can_read) {
foreach my $fh (@fhs) {
my $line = <$fh>;
unless(defined $line) {
$sel->remove($fh);
next;
}
if($fh == $out) {
print "[OUTPUT]: $line";
}elsif($fh == $err) {
print "[ERROR] : $line";
}else{
die "[ERROR]: This should never execute!";
}
}
}
waitpid($pid, 0);
}
这很好用,现在有些事情变得更清晰了。但整体情况仍然有点朦胧。
所以我的问题是:
hung_execute
出了什么问题?good_execute
因为open3调用中的>&
而有效。但是为什么以及如何?good_execute
而不是my $out
)来处理文件句柄时,OUT
不起作用。它给出了这个错误:open3: open(GLOB(0x610920), >&main::OUT) failed: Invalid argument
。为什么会这样?答案 0 :(得分:13)
你遇到过我在文档中写到的问题,然后是一些问题。你正在陷入困境,因为你在等待孩子读书之前就等了。如果它有超过输出的管道缓冲区,它将阻塞并下一次退出。另外,你没有关闭手柄的末端。
你也有其他错误。你不能用这种方式测试句柄上的输出,因为你只是做了一个阻塞读取线并丢弃了它的结果。此外,如果您尝试在stdout之前读取所有stderr,并且在stdout上输出的管道缓冲区不止,那么当您阻止从stderr读取时,您的孩子将阻止写入stdout。
您必须使用select
或IO::Select
来正确执行此操作。只有在该句柄上有可用输出时,您才能从句柄中读取,并且您不能将缓冲的呼叫与select
混合,除非您非常幸运。
答案 1 :(得分:7)
hung_execute
:
Parent Child
------------------------ ------------------------
Waits for child to exit
Writes to STDOUT
Writes to STDOUT
...
Writes to STDOUT
Tries to write to STDOUT
but the pipe is full,
so it blocks until the
pipe is emptied some.
死锁!
good_execute
:
Parent Child
------------------------ ------------------------
Waits for data
Writes to STDOUT
Reads the data
Waits for data
Writes to STDOUT
Reads the data
Waits for data
... ...
Writes to STDOUT
Reads the data
Waits for data
Exits, closing STDOUT
Reads EOF
Waits for child to exit
管子可能会变满,阻塞孩子;但父母很快就会把它清空,让孩子畅通无阻。没有死锁。
">&OUT"
评估为>&OUT
。 (没有要插入的变量)
">&$OUT"
评估为>&GLOB(0x########)
。 (您插入了$OUT
。)
有一种方法可以传递词法文件句柄(或者更确切地说是它的描述符),但是有一个关于它们的错误,所以我总是使用open3
的包变量。
STDOUT和STDERR是独立的(除非您执行2>&1
之类的操作,即便如此,它们也会有单独的标志和缓冲区)。如果您发现它们不是,那么您得出了错误的结论。