读取Perl管道子进程挂在句柄数组的最后一个元素上

时间:2017-05-04 05:02:06

标签: perl pipe fork

运行以下代码时,不打印最后一个服务器 - 脚本在第二个数组元素后面“挂起”。

my %readers;
my $command = "pgrep -f weblogic.Name";

foreach my $server(@servers) {
    pipe($readers{$server},WRITER);
    unless(my $pid = fork()) {
        my $response = qx(ssh -q oracle\@$server "$command");
        print WRITER $response;
        exit();
    }   
}


foreach my $server (@servers) {
     my $fh = $readers{$server};
     my @procs = <$fh>;
     chomp(@procs);
     for my $proc (@procs) {
          printf "%s\t%s\n", substr($server,8), $proc;
     }   
 }

 print "end\n";

输出如下:

$ ./get_stuck.pl  
92      18196
93      27420
94      17635
95      10258
96      10831

'96'之后应该有一个服务器'97'输出,但是没有,并且脚本只是挂起/停止。

如果我更改阅读器部分以使用字符串而不是数组,如下所示:

foreach my $server (@servers) {
    my $fh = $readers{$server};
    my $procs = <$fh>;
    printf "%s\n", $server;
}

...然后脚本打印所有服务器,包括'97',但是,如果命令有多个结果,这将只打印第一个结果(似乎在换行符中断)。换句话说,如果命令为给定服务器返回3个进程ID,则只打印第一个进程ID。

有关使用数组的原因的任何建议会导致脚本挂在最后一个元素上吗?或者也许(不太理想)我如何使用字符串,但检索所有结果?

1 个答案:

答案 0 :(得分:2)

我还没有尝试过,但是:

此代码看起来像你自己陷入僵局。

    列表上下文中的
  • < >会覆盖整个文件,即它会一直读到文件结尾(EOF)。
  • 有问题的文件句柄是指管道。
  • 当写入端的所有打开句柄都关闭时,管道的读取结束达到EOF。
  • 您永远不会在父级中关闭WRITER(子级会在退出时隐式关闭它)。
  • 因此,在保持写入结束打开的情况下,父级会从管道读取卡住。

这只发生在最后一个数组元素的原因是因为你正在使用一个裸字文件句柄(WRITER),它实际上是一个全局变量。重新打开相同的句柄会先隐式关闭它;即,循环的第(n + 1)次迭代关闭第n个管道。只剩下最后一个WRITER

如果我是对的,那么修复就是:

foreach my $server(@servers) {
    pipe($readers{$server},WRITER);
    unless(my $pid = fork()) {
        my $response = qx(ssh -q oracle\@$server "$command");
        print WRITER $response;
        exit();
    }
    close WRITER;  # always close WRITER in the parent
}

但我还建议将代码更改为:

foreach my $server (@servers) {
    pipe($readers{$server}, my $WRITER);
    defined(my $pid = fork()) or die "$0: fork: $!\n";
    unless($pid) {
        my $response = qx(ssh -q oracle\@$server "$command");
        print $WRITER $response;
        exit();
    }
    close $WRITER;
}

即。检查fork是否有错误并使用词法变量而不是裸字文件句柄。在这种情况下,close实际上是可选的,因为$WRITER在其范围的末尾(当前循环迭代)被隐式关闭,因为没有其他引用。

您可以使用管道打开来简化它:

foreach my $server (@servers) {
    open $readers{$server}, '-|', 'ssh', '-q', "oracle\@$server", $command
        or die "$0: ssh: $!\n";
}

最后,

my $fh = $readers{$server};
my @procs = <$fh>;

可以缩减为

my @proces = readline $readers{$server};

(我不喜欢< >运算符。在我看来,始终明确地写readlineglob会使其更具可读性。)