入出力ストリームを使用するJavaプロセス


90

以下のコード例があります。これにより、bashシェルにコマンドを入力しecho testて、結果をエコーバックできます。ただし、最初の読み取り後。他の出力ストリームは機能しませんか?

これはなぜですか、または何か間違っていますか?私の最終目標は、ように/ bashの定期コマンドを実行し作成したスレッドスケジュールされたタスクにあるOutputStreamInputStream作業を停止連携して仕事をしなければならないでしょう。私もエラーを経験していますjava.io.IOException: Broken pipeか?

ありがとう。

String line;
Scanner scan = new Scanner(System.in);

Process process = Runtime.getRuntime ().exec ("/bin/bash");
OutputStream stdin = process.getOutputStream ();
InputStream stderr = process.getErrorStream ();
InputStream stdout = process.getInputStream ();

BufferedReader reader = new BufferedReader (new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

String input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.close();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

「壊れたパイプ」は、おそらく子プロセスが終了したことを意味します。他のコードが何であるかを確認するために、残りのコードを完全に調べていません。
vanza

1
別のスレッドを使用すると、問題なく動作します
Johnydep

回答:


139

まず、ラインを交換することをお勧めします

Process process = Runtime.getRuntime ().exec ("/bin/bash");

線で

ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();

ProcessBuilderはJava 5の新機能であり、外部プロセスの実行を容易にします。私の意見では、最も大きな改善点Runtime.getRuntime().exec()は、子プロセスの標準エラーを標準出力にリダイレクトできることです。これはInputStream、読み取るものが1つしかないことを意味します。この前に、標準出力バッファーが空のときに(子プロセスがハングする)標準エラーバッファーがいっぱいにならないようにするため、またはstdoutから読み取りをstderr行うために、2つの別々のスレッドが必要でした。

次に、ループ(2つあります)

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

readerプロセスの標準出力から読み取るがファイルの終わりを返す場合にのみ終了します。これは、bashプロセスが終了したときにのみ発生します。現在、プロセスからの出力がなくなった場合、ファイルの終わりは返されません。代わりに、プロセスからの出力の次の行を待ち、この次の行があるまで戻りません。

このループに到達する前に2行の入力をプロセスに送信しているため、これらの2行の入力後にプロセスが終了しなかった場合、最初の2つのループはハングします。別の行が読み取られるのを待ちますが、読み取るための別の行はありません。

私はソースコードをコンパイルしました(現時点ではWindowsを使用しているので、に置き換え/bin/bashましたcmd.exeが、原則は同じです)、次のことがわかりました。

  • 2行入力すると、最初の2つのコマンドからの出力が表示されますが、プログラムがハングします。
  • 私は、言う、で入力した場合echo testと、その後、exit以来、プログラムは最初のループのそれを作るcmd.exeプロセスが終了しました。次に、プログラムは別の入力行を要求し(無視されます)、子プロセスが既に終了しているため、2番目のループを直接スキップし、それ自体を終了します。
  • と入力してexitecho test押すと、パイプが閉じていることを報告するIOExceptionが発生します。これは予想されることです。入力の1行目でプロセスが終了し、2行目を送信する場所がありません。

私が以前使用していたプログラムで、あなたが望んでいるように見えることと似たようなことをするトリックを見たことがあります。このプログラムはいくつかのシェルを保持し、それらの中でコマンドを実行し、これらのコマンドからの出力を読み取りました。使用されたトリックは、シェルコマンドの出力の終わりを示す「マジック」行を常に書き、それを使用して、シェルに送信されたコマンドからの出力がいつ終了したかを判断することでした。

私はあなたのコードを取り、割り当てられた行の後のすべてをwriter次のループで置き換えました:

while (scan.hasNext()) {
    String input = scan.nextLine();
    if (input.trim().equals("exit")) {
        // Putting 'exit' amongst the echo --EOF--s below doesn't work.
        writer.write("exit\n");
    } else {
        writer.write("((" + input + ") && echo --EOF--) || echo --EOF--\n");
    }
    writer.flush();

    line = reader.readLine();
    while (line != null && ! line.trim().equals("--EOF--")) {
        System.out.println ("Stdout: " + line);
        line = reader.readLine();
    }
    if (line == null) {
        break;
    }
}

これを実行した後、いくつかのコマンドを確実に実行し、各コマンドの出力を個別に返すことができました。

echo --EOF--シェルに送信された行の2つのコマンドは、コマンド--EOF--からのエラーの結果であっても、コマンドからの出力が確実に終了するようにするためのものです。

もちろん、このアプローチには限界があります。これらの制限は次のとおりです。

  • ユーザー入力を待機するコマンド(別のシェルなど)を入力すると、プログラムがハングしたように見える
  • シェルによって実行される各プロセスが出力を改行で終了すると想定しています
  • シェルによって実行されているコマンドがたまたま行を書き出すと、少し混乱し--EOF--ます。
  • bash構文エラーを報告し、不一致のテキストを入力すると終了します)

これらの点は、スケジュールされたタスクとして実行することを考えているものが、そのような病理学的な方法で動作しないコマンドまたはコマンドの小さなセットに制限される場合は、重要ではない場合があります。

編集:Linuxでこれを実行した後の終了処理とその他の小さな変更を改善


包括的な答えをありがとうございますしかし、私は私の問題の真のケースを識別したと思います。あなたの投稿で私の注意を引きました。stackoverflow.com/questions/3645889/…。ありがとうございました。
ジェームズムーア

1
/ bin / bashをcmdに置き換えても、同じように動作するプログラムを作成したため、実際には同じようには動作しませんでしたが、bashに問題がありました。mycaseでは、入力/出力/エラーごとに3つの別々のスレッドを開くことが、長いセッションの対話型コマンドでは問題なく最適に機能します。
Johnydep 2011


@AlexMills:2番目のコメントは問題が解決したことを意味しますか?最初のコメントには、何が問題なのか、またなぜ「突然」例外が発生しているのかを説明する詳細はありません。
Luke Woodward

@Luke、コメントの上に提供したリンクで問題が解決しました
Alexander Mills

4

デーモンスレッドのようなスレッドを使用して入力を読み取ることができ、出力リーダーは既にメインスレッドでwhileループに入っているので、同時に読み取りと書き込みを行うことができます。次のようにプログラムを変更できます。

Thread T=new Thread(new Runnable() {

    @Override
    public void run() {
        while(true)
        {
            String input = scan.nextLine();
            input += "\n";
            try {
                writer.write(input);
                writer.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }
} );
T.start();

読者は上記と同じになります

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

ライターをファイナルとして作成しないと、内部クラスからアクセスできなくなります。


1

あなたのwriter.close();コードにあります。そのため、bashはEOFを受け取りstdin、終了します。次に、無効なbash Broken pipeから読み取ろうとするstdoutと、

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.