101

次のように、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呼び出す新しいスレッドを手動で生成することです。write()System.out

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 つ増えます。これを行うより良い方法はありますか?

4

12 に答える 12

162

を使用する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 になるため、スレッドは自動的に終了します。

于 2013-01-05T02:54:23.117 に答える
73

Java 7 以降については、Evgeniy Dorofeev の回答を参照してください。

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();
        }
    }
}
于 2013-01-04T21:50:57.223 に答える
21

Consumer行ごとに出力を処理する (例: ログに記録する)を提供できる、Java 8 ラムダを使用した柔軟なソリューション。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

于 2015-10-28T08:59:06.927 に答える
16

次のように簡単です。

    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 スレッドの両方の出力を確認できるようになりました。

于 2016-11-08T11:20:39.490 に答える
1

msangel の回答への追加として、次のコード ブロックを追加したいと思います。

private static CompletableFuture<Boolean> redirectToLogger(final InputStream inputStream, final Consumer<String> logLineConsumer) {
        return CompletableFuture.supplyAsync(() -> {
            try (
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            ) {
                String line = null;
                while((line = bufferedReader.readLine()) != null) {
                    logLineConsumer.accept(line);
                }
                return true;
            } catch (IOException e) {
                return false;
            }
        });
    }

プロセスの入力ストリーム (stdout、stderr) を他のコンシューマにリダイレクトできます。これは、System.out::println または文字列を使用するその他のものである可能性があります。

使用法:

...
Process process = processBuilder.start()
CompletableFuture<Boolean> stdOutRes = redirectToLogger(process.getInputStream(), System.out::println);
CompletableFuture<Boolean> stdErrRes = redirectToLogger(process.getErrorStream(), System.out::println);
System.out.println(stdOutRes.get());
System.out.println(stdErrRes.get());
System.out.println(process.waitFor());
于 2020-03-06T10:21:50.750 に答える
1

のリダイレクト メソッドが, のみProcessBuilderを受け入れないことは、私にとって本当に驚くべきことです。Java が強制的に定型コードを書くことを強いるもう 1 つの証拠です。OutputStreamFile

とはいえ、包括的なオプションのリストを見てみましょう。

  1. プロセス出力を単にその親の出力ストリームにリダイレクトする場合inheritIOは、ジョブを実行します。
  2. プロセスの出力をファイルに出力する場合は、redirect*(file).
  3. プロセスの出力をロガーに送りたい場合InputStreamは、別のスレッドでプロセスを消費する必要があります。Runnableまたはを使用する回答を参照してくださいCompletableFuture。これを行うために、以下のコードを適合させることもできます。
  4. プロセスの出力をPrintWriter標準出力である場合とそうでない場合がある (テストに非常に便利) に出力する場合は、次のようにします。
static int execute(List<String> args, PrintWriter out) {
    ProcessBuilder builder = new ProcessBuilder()
            .command(args)
            .redirectErrorStream(true);
    Process process = null;
    boolean complete = false;
    try {
        process = builder.start();
        redirectOut(process.getInputStream(), out)
                .orTimeout(TIMEOUT, TimeUnit.SECONDS);
        complete = process.waitFor(TIMEOUT, TimeUnit.SECONDS);
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    } catch (InterruptedException e) {
        LOG.warn("Thread was interrupted", e);
    } finally {
        if (process != null && !complete) {
            LOG.warn("Process {} didn't finish within {} seconds", args.get(0), TIMEOUT);
            process = process.destroyForcibly();
        }
    }

    return process != null ? process.exitValue() : 1;
}

private static CompletableFuture<Void> redirectOut(InputStream in, PrintWriter out) {
    return CompletableFuture.runAsync(() -> {
        try (
                InputStreamReader inputStreamReader = new InputStreamReader(in);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader)
        ) {
            bufferedReader.lines()
                    .forEach(out::println);
        } catch (IOException e) {
            LOG.error("Failed to redirect process output", e);
        }
    });
}

これまでの他の回答に対する上記のコードの利点:

  1. redirectErrorStream(true)エラー ストリームを出力ストリームにリダイレクトするので、1 つのみを処​​理するだけで済みます。
  2. CompletableFuture.runAsyncから実行されますForkJoinPool。このコードは、getまたはjoinを呼び出すことによってブロックされませんCompletableFutureが、完了時に代わりにタイムアウトを設定することに注意してください (Java 9 以降)。CompletableFuture.supplyAsyncメソッドから返されるものは実際には何もないため、必要はありませんredirectOut
  3. BufferedReader.lineswhileループを使用するよりも簡単です。
于 2021-01-10T02:44:02.810 に答える
-1
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {

    public static void main(String[] args) throws Exception {
        ProcessBuilder pb = new ProcessBuilder("script.bat");
        pb.redirectErrorStream(true);
        Process p = pb.start();
        BufferedReader logReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String logLine = null;
        while ( (logLine = logReader.readLine()) != null) {
           System.out.println("Script output: " + logLine);
        }
    }
}

この行を使用pb.redirectErrorStream(true);すると、InputStream と ErrorStream を組み合わせることができます。

于 2020-10-26T18:46:30.340 に答える
-2

デフォルトでは、作成されたサブプロセスには独自の端末またはコンソールがありません。すべての標準 I/O (stdin、stdout、stderr) 操作は親プロセスにリダイレクトされ、メソッド getOutputStream()、getInputStream()、および getErrorStream() を使用して取得したストリームを介してアクセスできます。親プロセスは、これらのストリームを使用して、サブプロセスに入力をフィードし、サブプロセスから出力を取得します。一部のネイティブ プラットフォームでは、標準の入力ストリームと出力ストリームに対して限られたバッファー サイズしか提供されないため、サブプロセスの入力ストリームの書き込みまたは出力ストリームの読み取りが迅速に行われないと、サブプロセスがブロックされたり、デッドロックが発生したりする可能性があります。

https://www.securecoding.cert.org/confluence/display/java/FIO07-J.+Do+not+let+external+processes+block+on+IO+buffers

于 2015-10-01T17:21:43.393 に答える