2017-05-04 4 views
0

次のコードを実行すると、最後のサーバーは印刷されません。スクリプトは最後の2番目の配列要素の後にハングします。ハンドル配列の最後の要素でハングするPerlパイプの子プロセスからの読み取り

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

サーバー「96」の後に「97」出力があるはず、まだ存在しない、とスクリプトはちょうど/はその時点で停止ハング:

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

出力は次のようです。私は、配列の代わりに文字列を使用するようにリーダー部を変更した場合

次のように:から複数の結果がある場合

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

が...その後、スクリプトは、しかし、「97」を含むすべてのサーバーを表示しますコマンドでは、これは最初の結果だけを出力します(改行を改行しているようです)。言い換えれば、コマンドが特定のサーバーに対して3つのプロセスIDを返す場合、最初のプロセスIDだけが出力されます。

なぜ配列を使用すると、最後の要素でスクリプトがハングするのですか?または、私は文字列をどのように使用するかもしれないか(あまり望ましくない)、すべての結果を取得しますか?

答えて

2

実際に試したことはありませんが、

このコードはあなたがデッドロックしているようです。リストコンテキストで

  • <>は、エンド・オブ・ファイル(EOF)に達するまで、すなわち、それは読み出し、ファイル全体をslurps。
  • 問題のファイルハンドルはパイプを参照しています。
  • パイプの読み取り側は、書き込み側のすべてのオープンハンドルが閉じられたときに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}; 

に減少させることができた(私は好きではありません<>オペレータ。私の意見では常にどちらか書いてくださいreadlineまたはglobは明示的に読みやすくしています。)

+0

私は未書込みのWRITERも考えました。私はまだ、スカラーが '<$fh>'でそれをどう読むかわかりません...私はどこかでそれを逃していますか? – zdim

+0

@zdimスカラーコンテキストでは、最初の行だけを取得します(または、OPの言葉では、 "*ただし、コマンドから複数の結果がある場合、改行で改行されたように見えます)*")、リストコンテキストはすべての行を読み込み、EOFに達するまでブロックする必要があります。 – melpomene

+0

優秀!私はWRITERを閉じようとしましたが、 'unless'ステートメントの中でやっていました。私はあなたが示唆したように字句変数を使用するように自分のコードを更新しましたが、明快にするために明示的な 'close'文を含んでいます。あなたの助けをありがとう。チャンピオン。 – Stu

関連する問題