web-dev-qa-db-ja.com

Java入力/出力ストリームを持つプロセス

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

なぜこれなのか、何か間違っているのですか?私の最終目標は、OutputStreamInputStreamが連携して動作する必要があり、動作を停止しないように、定期的に/ bashにコマンドを実行するスレッドスケジュールタスクを作成することです。エラー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);
}
89
James moore

まず、行を置き換えることをお勧めします

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/bashcmd.exeに置き換えましたが、原則は同じである必要があります)。

  • 2行で入力すると、最初の2つのコマンドからの出力が表示されますが、プログラムがハングします。
  • たとえば、echo testと入力してからexitと入力すると、cmd.exeプロセスが終了したため、プログラムは最初のループから抜け出します。次に、プログラムは入力の別の行(これは無視されます)を要求し、子プロセスが既に終了しているため、2番目のループを直接スキップしてから終了します。
  • exitを入力してからecho testを入力すると、パイプが閉じられていることを訴えるIOExceptionが表示されます。これは予想されることです-入力の最初の行によってプロセスが終了し、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;
    }
}

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

シェルに送信された行の2つのecho --EOF--コマンドは、コマンドからのエラーの結果であっても、コマンドからの出力が--EOF--で終了することを保証するためにあります。

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

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

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

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

134
Luke Woodward

入力を読み取るためにdemon-threadのようなスレッドを使用でき、出力リーダーはすでにメインスレッドの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);
}

そうしないと、内部クラスからアクセスできなくなります。

4
Shashank Jain

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

1
gpeche