以下のコード例があります。これにより、bashシェルにコマンドを入力できます(つまり、echo test
)、結果をエコーバックできます。ただし、最初の読み取りの後。他の出力ストリームは機能しませんか?
なぜこれなのか、何か間違っているのですか?私の最終目標は、OutputStream
とInputStream
が連携して動作する必要があり、動作を停止しないように、定期的に/ 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);
}
まず、行を置き換えることをお勧めします
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
に置き換えましたが、原則は同じである必要があります)。
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でこれを実行した後の終了処理とその他の小さな変更を改善します。
入力を読み取るために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);
}
そうしないと、内部クラスからアクセスできなくなります。
コードにwriter.close();
があります。そのため、bashはstdin
でEOFを受け取り、終了します。次に、無効なbashのstdout
から読み取ろうとすると、Broken pipe
を取得します。