7

次のようなJavaコード内からスクリプトを実行しようとしています。

Process p = Runtime.getRuntime().exec(cmdarray, envp, dir); // cmdarray is a String array
// consisting details of the script and its arguments

final Thread err = new Thread(...); // Start reading error stream
err.start();
final Thread out = new Thread(...); // Start reading output stream
out.start();
p.waitFor();
// Close resources 

スクリプトの実行は終了しましたが(pidはもうありません)、JavaはwaitFor()プロセスのメソッドでスタックしています!。はい、2つの別々のスレッドで出力ストリームとエラーストリームを読み取っています。はい、それらは最後に(後waitFor())結合されています。

スクリプトは基本的にいくつかのRPM(10程度など)をインストールし、それらを構成します。したがって、スクリプトは60秒強実行されます。

次のようになります。

#!/bin/sh

#exec 3>&1 >/var/log/some_log 2>&1

# If the above line is uncommented, Java recognizes that the 
# process is over and terminates fine.

tar xzf a-package-having-rpms.tar.gz
cd unpacked-folder
(sh installer-script.sh) #This installs 10 odd rpms in a subshell and configures them
cd ..
rm -rf unpacked-folder

exit 0

驚いたことに、スクリプト(上部)に次の行を入れると、Javaはスクリプトが終了したことを認識し、プロセスを完全に終了します。

exec 3>&1 > /var/log/some_log 2>&1

記録のために、スクリプトは出力を生成しません。ゼロ文字!。したがって、execステートメントをそこに置くことは意味がありません!

しかし、それでも、魔法のように、スクリプトにexecステートメントを入れると、Javaが機能します。なぜ??

スクリプトでその非論理的なexecステートメントを回避するにはどうすればよいですか?

Installer-script.shがどのように見えるかに興味がある場合は、次のようにします。

#!/bin/sh

exec 3>&1 >>/var/log/another-log.log 2>&1
INSDIR=$PWD
RPMSDIR=$INSDIR/RPMS
cd $RPMSDIR
#
rpm -i java-3.7.5-1.x86_64.rpm
rpm -i --force perl-3.7.5-1.x86_64.rpm
rpm -i --nodeps mysql-libs-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps mysql-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps mysql-server-5.0.51a-1.vs.i386.rpm
rpm -i --nodeps perl-DBD-MySQL-3.0007-2.el5.i386.rpm
rpm -i --nodeps perl-XML-Parser-2.34-6.1.2.2.1.i386.rpm
.
.
.

では、Javaがプロセスが終了したことを知るために、最初のスクリプトのexecコマンドが必要なのはなぜですか?どうすればその幹部を避けることができますか?、特に。最初のスクリプトは出力を生成しないためです。

息を切らして答えを待っています!

4

7 に答える 7

8

私の推測では、Javaは、スクリプトがstdin / stdout / stderrを介して渡されたパイプがサブプロセスによって閉じられるまで、スクリプトが終了したとは見なしません。つまり、stdinにはアクティブなリーダープロセスはなく、stdout/stderrにはアクティブなライタープロセスはありません。

パイプを読み取っているとき、出力用にパイプを開いているプロセスがなくなるまで、ファイルの終わりの表示は表示されません。したがって、プロセスがフォークし、新しいプロセスが開いているファイルハンドルを継承する場合、元のプロセスは終了し、ファイルが開いているプロセスがまだ存在し、リーダーは待機します。

同様に、作成しているパイプの場合、最後のリーダーがパイプを閉じるまで、「パイプの破損」信号を受信しません。

この問題は通常、スクリプトがstdin / stdout / stderrを継承するバックグラウンドタスク(新しくインストールされたサービスなど)をフォークするときに発生します。

execを使用すると、これらのパイプの継承チェーンを明示的に切断して、バックグラウンドプロセスがそれらを使用しないようにします。

Linuxの場合は、/ proc / * / fdで新しいサービスを確認し、それらのstdin / stdout/stderrがJavaプロセスがスクリプトに渡すパイプと同じであるかどうかを確認します。

/etc/init.d/xxxスクリプトを実行すると、同じ状況がよく発生します。コマンドラインから実行すると正常に完了しますが、ある種のモニターから実行するとハングしているように見えます。

編集:

インストーラスクリプトに次の行が含まれていると言います。

exec 3>&1 >>/var/log/another-log.log 2>&1

最初の用語3>&1は、stdoutをファイル記述子3に複製します(man bashのリダイレクトを参照)。私の知る限り、fd3には特別な意味はありません。次に、/ var / log / another-log.logを開いてstdoutを置き換え、stdoutのクローンを作成してstderrを置き換えます。bashのmanページの「リダイレクト」セクションを参照してください。

これは、新しいファイル記述子3が、元々STDOUTとして渡されたパイプに書き込むために開かれていることを意味します。システムサービスデーモンであることが期待されるプログラムは、多くの場合、ファイル記述子0(STDIN)、1(STDOUT)、および2(STDERR)を閉じますが、他の記述子を気にすることはありません。また、シェルがFD-3を開いたので、その開いたファイルを、バックグラウンドコマンドを含む実行するすべてのコマンドに渡します。

インストーラーがFD3を開く特別な理由があるかどうか知っていますか?私の推測では、インストーラーから「3>&1」という用語を削除するだけで、問題は解決するでしょう。これにより、スクリプトからexecを完全に削除できます

于 2010-08-14T23:40:06.913 に答える
1

考えられる原因は、パッケージをインストールすると、パイプ(stdout / stderr)の一方または両方を開いたままにするバックグラウンドプロセスが開始されることです。したがって、スレッドは終了しません。これは、これらのバックグラウンドプロセスが適切にデーモン化されないことを意味します。適切なデーモンは、初期化が完了した後、元のstdin / stdout/stderrを/dev/nullまたはログファイルに置き換えます。

于 2010-08-14T23:30:02.127 に答える
0

問題の一部を理解しているように聞こえますが、それでも「全体像」を十分に把握できていません。

  1. はい、Javaプログラムからシェルスクリプトを呼び出すことは可能です。実際、簡単です!<=あなたはすでにこれを知っています。

  2. はい、通常、stderrまたはstdoutに印刷されたものはすべて表示できるはずです。

  3. はい、stderrやstdoutをファイルにリダイレクトできるはずです。

  4. あなたの問題は明らかに、あなたがそれを見ることを期待しているときに、あなたが期待しているものを見ていなかったことです。これは正常であり、予想されます...そして、より大きなスキームでは、これは非常に望ましいことです。

中心的な問題は「I/Oバッファリング」だと思います。

バッファリングは良いことです。

次のことを強くお勧めします。1。シェルスクリプトを呼び出す単純なシングルスレッドのコマンドラインJavaテストプログラムを作成します。<=「期待される結果」が表示されるはずです

  1. スレッドを使用してテストプログラムを作成します。プログラムの構造によっては、「期待どおりの結果」が得られる場合と得られない場合があります。

  2. シェルスクリプトの代わりにCプログラム(または別のJavaプログラム)を使用する<=これにより、「stdout」と比較して、バッファリングされていない「stderr」を使いやすくなります。

  3. 実験して、あなたが学んだことを見てください。

幸運を祈ります-そして、中途半端にもっともらしいと思われる最初の結論にジャンプしないでください。常にあなたの理論をテストしてください。

PS:そして私のマントラを繰り返してください:「I/Oバッファリングは良いです」;-)

PPS:

Q:この質問はこれまでに38回しか表示されていません!

A:「あなたは忍耐を学ばなければなりません、若いバッタ」;-)

Q:これは私にとってちょっと重要です!

A:「謙虚さも学ばなければならない」;-)

于 2010-08-15T21:25:06.667 に答える
0

Linux / Unixで実行している場合は、stdout/stderrを使用しないようにしてください。

前回Javaから外部スクリプトを実行する必要があったときは、OSをチェックし、Windowsの場合にのみ出力イータースレッドをアタッチする必要がありました。

于 2010-08-14T17:39:55.913 に答える
0

また、標準入力もポンピングします。3つの標準ストリームすべてをポンピングすることをお勧めします。説明することはできませんが、以前にプロセスがハングするという同様の問題を経験しました。問題となったのはstdinポンプの欠如でした。

于 2010-08-14T17:49:49.653 に答える
0

Linuxで試したことはありませんが、Windowsでこの問題が発生しました。プロセスが終了する前に、プロセスの標準をストリームで閉じる必要があったと思います。

于 2010-08-14T18:17:04.780 に答える
-1

Javaからスクリプトを実行してそれを待つことはできません。そのスクリプトのインタプリタを実行する必要があります。cmdarrayは、「myscript.bash」ではなく「bash」で始まる必要があります。

于 2010-08-14T22:57:51.083 に答える