【问题标题】:Reading from Perl pipe child process hanging on last element of handle array从挂在句柄数组的最后一个元素上的 Perl 管道子进程读取
【发布时间】:2017-10-02 02:56:57
【问题描述】:

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

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。

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

【问题讨论】:

    标签: perl pipe fork


    【解决方案1】:

    我还没有真正尝试过,但是:

    这段代码看起来像是你自己陷入了僵局。

    • 列表上下文中的&lt;&gt; 会读取整个文件,即它会一直读取到文件结尾 (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 在其范围(当前循环迭代)结束时隐式关闭,因为没有其他对它的引用。

    你可以通过使用 pipe open 来简化它:

    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};
    

    (我不喜欢 &lt; &gt; 运算符。在我看来,总是明确地写 readlineglob 会使其更具可读性。)

    【讨论】:

    • 我也想到了未关闭的WRITER。我仍然看不到 &lt;$fh&gt; 上读取的标量如何改变它......我在某个地方错过了吗?
    • @zdim 在标量上下文中,您只能得到第一行(或者用 OP 的话来说,“但是,如果命令有多个结果,这只会打印第一个结果(似乎break on newline)"),而列表上下文将读取所有行,这需要阻塞直到到达 EOF。
    • 太棒了!我确实尝试关闭 WRITER,但我是在 unless 语句中完成的,而不是在它之后。我已经按照您的建议更新了我的代码以使用词法变量,但为了清楚起见,仍然包含显式的 close 语句。非常感谢您的帮助。冠军。
    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    相关资源
    最近更新 更多