次のようにProcessBuilderを使用してJavaでプロセスを構築しています。
ProcessBuilder pb = new ProcessBuilder()
.command("somecommand", "arg1", "arg2")
.redirectErrorStream(true);
Process p = pb.start();
InputStream stdOut = p.getInputStream();
ここで私の問題は次のとおりです。そのプロセスのstdoutおよび/またはstderrを通過しているものをキャプチャし、System.out
に非同期にリダイレクトしたいと思います。プロセスとその出力リダイレクトをバックグラウンドで実行したい。これまでのところ、これを行うことがわかった唯一の方法は、stdOut
から継続的に読み取る新しいスレッドを手動で生成し、System.out
の適切なwrite()
メソッドを呼び出すことです。
new Thread(new Runnable(){
public void run(){
byte[] buffer = new byte[8192];
int len = -1;
while((len = stdOut.read(buffer)) > 0){
System.out.write(buffer, 0, len);
}
}
}).start();
そのアプローチは一種の作品ですが、少し汚い感じがします。それに加えて、正しく管理して終了するスレッドがもう1つあります。これを行うより良い方法はありますか?
Java 6またはそれ以前でのみ使用するには、いわゆるStreamGobbler
(作成を開始)を使用します。
StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), "ERROR");
// any output?
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), "OUTPUT");
// start gobblers
outputGobbler.start();
errorGobbler.start();
...
private class StreamGobbler extends Thread {
InputStream is;
String type;
private StreamGobbler(InputStream is, String type) {
this.is = is;
this.type = type;
}
@Override
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
System.out.println(type + "> " + line);
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Java 7については、Evgeniy Dorofeevの回答を参照してください。
ProcessBuilder.inheritIO
を使用すると、サブプロセスの標準I/Oのソースと宛先が現在のJavaプロセスのソースと宛先と同じに設定されます。
Process p = new ProcessBuilder().inheritIO().command("command1").start();
Java 7がオプションではない場合
public static void main(String[] args) throws Exception {
Process p = Runtime.getRuntime().exec("cmd /c dir");
inheritIO(p.getInputStream(), System.out);
inheritIO(p.getErrorStream(), System.err);
}
private static void inheritIO(final InputStream src, final PrintStream dest) {
new Thread(new Runnable() {
public void run() {
Scanner sc = new Scanner(src);
while (sc.hasNextLine()) {
dest.println(sc.nextLine());
}
}
}).start();
}
src
はEOFになるため、サブプロセスが終了するとスレッドは自動的に終了します。
Java 8ラムダを使用した柔軟なソリューションで、1行ごとに出力を処理する(ログに記録するなど)Consumer
を提供できます。 run()
は、チェック例外がスローされないワンライナーです。 Runnable
を実装する代わりに、他の回答が示唆するように、代わりにThread
を拡張できます。
class StreamGobbler implements Runnable {
private InputStream inputStream;
private Consumer<String> consumeInputLine;
public StreamGobbler(InputStream inputStream, Consumer<String> consumeInputLine) {
this.inputStream = inputStream;
this.consumeInputLine = consumeInputLine;
}
public void run() {
new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumeInputLine);
}
}
次に、たとえば次のように使用できます。
public void runProcessWithGobblers() throws IOException, InterruptedException {
Process p = new ProcessBuilder("...").start();
Logger logger = LoggerFactory.getLogger(getClass());
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), System.out::println);
StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), logger::error);
new Thread(outputGobbler).start();
new Thread(errorGobbler).start();
p.waitFor();
}
ここで、出力ストリームはSystem.out
にリダイレクトされ、エラーストリームはlogger
によってエラーレベルで記録されます。
次のように簡単です。
File logFile = new File(...);
ProcessBuilder pb = new ProcessBuilder()
.command("somecommand", "arg1", "arg2")
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(logFile);
.redirectErrorStream(true)によってプロセスにエラーと出力ストリームのマージを指示し、.redirectOutput(file)によってマージされた出力をファイルにリダイレクトします。
更新:
私は次のようにこれを行うことができました:
public static void main(String[] args) {
// Async part
Runnable r = () -> {
ProcessBuilder pb = new ProcessBuilder().command("...");
// Merge System.err and System.out
pb.redirectErrorStream(true);
// Inherit System.out as redirect output stream
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
try {
pb.start();
} catch (IOException e) {
e.printStackTrace();
}
};
new Thread(r, "asyncOut").start();
// here goes your main part
}
これで、System.outのメインスレッドとasyncOutスレッドの両方の出力を確認できます
私もJava 6のみを使用できます。@ EvgeniyDorofeevのスレッドスキャナー実装を使用しました。私のコードでは、プロセスが終了した後、リダイレクトされた出力を比較する2つのプロセスをすぐに実行する必要があります(stdoutとstderrが祝福されたものと同じであることを確認するdiffベースのユニットテスト)。
プロセスが完了するまでwaitFor()しても、スキャナースレッドはすぐに終了しません。コードを正しく機能させるには、プロセスが終了した後にスレッドが結合されるようにする必要があります。
public static int runRedirect (String[] args, String stdout_redirect_to, String stderr_redirect_to) throws IOException, InterruptedException {
ProcessBuilder b = new ProcessBuilder().command(args);
Process p = b.start();
Thread ot = null;
PrintStream out = null;
if (stdout_redirect_to != null) {
out = new PrintStream(new BufferedOutputStream(new FileOutputStream(stdout_redirect_to)));
ot = inheritIO(p.getInputStream(), out);
ot.start();
}
Thread et = null;
PrintStream err = null;
if (stderr_redirect_to != null) {
err = new PrintStream(new BufferedOutputStream(new FileOutputStream(stderr_redirect_to)));
et = inheritIO(p.getErrorStream(), err);
et.start();
}
p.waitFor(); // ensure the process finishes before proceeding
if (ot != null)
ot.join(); // ensure the thread finishes before proceeding
if (et != null)
et.join(); // ensure the thread finishes before proceeding
int rc = p.exitValue();
return rc;
}
private static Thread inheritIO (final InputStream src, final PrintStream dest) {
return new Thread(new Runnable() {
public void run() {
Scanner sc = new Scanner(src);
while (sc.hasNextLine())
dest.println(sc.nextLine());
dest.flush();
}
});
}
Thread thread = new Thread(() -> {
new BufferedReader(
new InputStreamReader(inputStream,
StandardCharsets.UTF_8))
.lines().forEach(...);
});
thread.start();
...
の代わりにカスタムコードが使用されます
CompletableFuture
を使用して出力とリアクティブ処理の両方をキャプチャするシンプルなJava8ソリューション:
static CompletableFuture<String> readOutStream(InputStream is) {
return CompletableFuture.supplyAsync(() -> {
try (
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
){
StringBuilder res = new StringBuilder();
String inputLine;
while ((inputLine = br.readLine()) != null) {
res.append(inputLine).append(System.lineSeparator());
}
return res.toString();
} catch (Throwable e) {
throw new RuntimeException("problem with executing program", e);
}
});
}
そして使用法:
Process p = Runtime.getRuntime().exec(cmd);
CompletableFuture<String> soutFut = readOutStream(p.getInputStream());
CompletableFuture<String> serrFut = readOutStream(p.getErrorStream());
CompletableFuture<String> resultFut = soutFut.thenCombine(serrFut, (stdout, stderr) -> {
// print to current stderr the stderr of process and return the stdout
System.err.println(stderr);
return stdout;
});
// get stdout once ready, blocking
String result = resultFut.get();