Java ProcessBuilder:转发已启动进程的stdout和stderr,而不阻塞主线程

Java ProcessBuilder:转发已启动进程的stdout和stderr,而不阻塞主线程,java,processbuilder,Java,Processbuilder,我使用ProcessBuilder在Java中构建一个流程,如下所示: ProcessBuilder pb = new ProcessBuilder() .command("somecommand", "arg1", "arg2") .redirectErrorStream(true); Process p = pb.start(); InputStream stdOut = p.getInputStream(); public static void mai

我使用ProcessBuilder在Java中构建一个流程,如下所示:

ProcessBuilder pb = new ProcessBuilder()
        .command("somecommand", "arg1", "arg2")
        .redirectErrorStream(true);
Process p = pb.start();

InputStream stdOut = p.getInputStream();
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
}
现在我的问题是:我想捕获通过该进程的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();

虽然这种方法可行,但感觉有点脏。最重要的是,它给了我更多的线程来管理和正确终止。有更好的方法吗?

有关Java 7和更高版本的,请参阅Evgeniy Dorofev的

对于Java 6及更早版本,创建并使用
StreamGobbler

StreamGobbler错误gobbler=
新的StreamGobbler(p.getErrorStream(),“ERROR”);
//有输出吗?
StreamGobbler输出Gobbler=
新的StreamGobbler(p.getInputStream(),“OUTPUT”);
//开始狼吞虎咽
outputGobbler.start();
errorGobbler.start();

私有类StreamGobbler扩展线程{
输入流为;
字符串类型;
私有StreamGobbler(InputStream为,字符串类型){
this.is=is;
this.type=type;
}
@凌驾
公开募捐{
试一试{
InputStreamReader isr=新的InputStreamReader(is);
BufferedReader br=新的BufferedReader(isr);
字符串行=null;
而((line=br.readLine())!=null)
系统输出打印项次(类型+“>”+行);
}
捕获(ioe异常ioe){
ioe.printStackTrace();
}
}
}

使用
ProcessBuilder.inheritaio
,它将子流程标准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 6。我使用了@EvgeniyDorofeev的线程扫描程序实现。在我的代码中,在一个进程完成后,我必须立即执行另外两个进程,每个进程都比较重定向的输出(一个基于差异的单元测试,以确保stdout和stderr与其他进程相同)

即使我等待()进程完成,扫描线程也不会很快完成。为了使代码正常工作,我必须确保在进程完成后连接线程

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();
        }
    });
}

默认情况下,创建的子流程没有自己的终端或控制台。它的所有标准I/O(即stdin、stdout、stderr)操作都将重定向到父进程,在父进程中,可以通过使用getOutputStream()、getInputStream()和getErrorStream()方法获得的流来访问它们。父进程使用这些流向子进程提供输入并从中获取输出。由于某些本机平台仅为标准输入和输出流提供有限的缓冲区大小,因此未能及时写入子流程的输入流或读取子流程的输出流可能会导致子流程阻塞,甚至死锁


一个使用Java 8 lambda的灵活解决方案,允许您提供一个
消费者
,逐行处理输出(如记录)
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);
    }
}

在这里,输出流被重定向到
System.out
,错误流由
记录器记录在错误级别上,如下所示:

    File logFile = new File(...);
    ProcessBuilder pb = new ProcessBuilder()
        .command("somecommand", "arg1", "arg2")
    processBuilder.redirectErrorStream(true);
    processBuilder.redirectOutput(logFile);
通过.redirectErrorStream(true)告诉进程将错误和输出流合并到中,然后通过.redirectOutput(file)将合并的输出重定向到文件中

更新:

我确实做到了以下几点:

ProcessBuilder pb = new ProcessBuilder()
        .command("somecommand", "arg1", "arg2")
        .redirectErrorStream(true);
Process p = pb.start();

InputStream stdOut = p.getInputStream();
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中看到主线程和异步线程的输出


您的自定义代码取代了简单的java8解决方案,它使用CompletableFuture捕获输出和响应处理:

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);
        }
    });
}
静态CompletableFuture readOutStream(InputStream为){
返回CompletableFuture.SupplySync(()->{
试一试(
InputStreamReader isr=新的InputStreamReader(is);
BufferedReader br=新的BufferedReader(isr);
) {
StringBuilder res=新的StringBuilder();
字符串输入线;
而((inputLine=br.readLine())!=null){
res.append(inputLine).append(System.lineSeparator());
}
return res.toString();
}捕获(可丢弃的e){
抛出新的运行时异常(“执行程序的问题”,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();
processp=Runtime.getRuntime().exec(cmd);
CompletableFuture-soutFut=readOutStream(p.getInputStream());
CompletableFuture serrFut=readOutStream(p.getErrorStream());
CompletableFuture ResultOut=
soutFut.thenCombine(serrFut,(stdout,stderr)->{
//将进程的标准打印到当前标准并返回标准输出
系统错误println(stderr);
返回标准输出;
});
//一旦准备好了,就开始拦截
String result=resultput.get();
作为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;
            }
        });
    }
private static CompletableFuture redirectToLogger(最终InputStream InputStream,最终使用者logLineConsumer){
返回CompletableFuture.SupplySync(()->{
试一试(
InputStreamReader InputStreamReader=新的InputStreamReader(inputStream);
BufferedReader BufferedReader=新的BufferedReader(inp
...
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());
<dependency>
  <groupId>org.zeroturnaround</groupId>
  <artifactId>zt-exec</artifactId>
  <version>1.11</version>
</dependency>
new ProcessExecutor()
  .command("somecommand", "arg1", "arg2")
  .redirectOutput(System.out)
  .redirectError(System.err)
  .execute();
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);
        }
    }
}
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);
        }
    });
}