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

问题描述:

运行以下代码时,最后一个服务器不会被打印 - 脚本在倒数第二个数组元素之后“挂起”。读取挂在句柄数组最后一个元素上的Perl管道子进程

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。

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

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

此代码看起来像你自己死锁。

  • <>在列表环境吸食整个文件,即,它读取,直到它到达结束文件(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明确地使其更具可读性。)

+0

我还想到了未封闭的'WRITER'。我仍然看不到''上的标量读数是如何改变的......我是否错过了某处? – zdim

+0

@zdim在标量环境下,你只能得到第一行(或者在OP的话中,“*但是,如果命令有多个结果,这将只打印第一个结果(似乎在换行符上打断)*”),而列表上下文将读取所有行,在达到EOF之前需要阻塞。 – melpomene

+0

优秀!我曾尝试关闭WRITER,但我在'除非'声明中做了,而不是在它之后。我已经更新了我的代码,按照您的建议使用了一个词法变量,但为了清晰起见,仍然包含了明确的“close”语句。非常感谢您的帮助。冠军。 – Stu