使用Java实时获取进程输出

3
我希望能在Java中打印进程的实时输出,但该输出(几乎)只有当进程停止执行时才可用。
换句话说,当我使用命令行运行相同的进程时,我获得更频繁的反馈(输出),而当我使用Java执行它时,则不是这样。
我测试了以下代码以每250ms打印一次输出:
private static String printStream(BufferedReader bufferedReader) {
    try {
        String line = bufferedReader.readLine();
        if(line != null) {
            System.out.println(line);
        }
        return line;
    } catch (IOException ex) {
        ex.printStackTrace();
    }
    return null;
}

public static void sendProcessStreams(Process process, SendMessage sendMessage) {
    String[] command = { "/bin/bash", "-c", "custom_process"};
    ProcessBuilder processBuilder = new ProcessBuilder(command);
    processBuilder.directory(new File("."));
    Process process = processBuilder.start();

    BufferedReader inputBufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 1);
    BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()), 1);
    System.out.println("Start reading process");
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
          @Override
          public void run() {
              String inputLine = printStream(inputBufferedReader);
              String errorLine = printStream(errorBufferedReader);
              if(inputLine == null && errorLine == null && !process.isAlive()) {
                  timer.cancel();
              }
          }
     }, 0, 250);
}

然而它只打印两行,然后等待进程结束之前再打印其余内容。

是否有可能从外部进程获得更频繁的反馈?如果不能,为什么?


1
你试过直接使用 InputStreamReader 吗?如果你想要实时操作,为什么要进行缓冲呢? - Malt
你尝试过将“redirectOutput”重定向到标准输出吗? - jhamon
我使用了 BufferedReader,因为文档中说“为了最高效率,请考虑在 BufferedReader 中包装 InputStreamReader。”;我尝试使用了较小的缓冲区大小... 但我对此并不自信,希望文档能提供更多信息。谢谢! 我没有尝试使用 redirectOutput,因为我希望通过 WebSocket 发送流,而不是标准输出。 - arthur.sw
2个回答

3

就从输出读取数据而言,您的代码基本上是正确的,问题肯定是由于其他原因引起的,请构建一个最小化的可重现问题(例如, 什么是custom_process,为什么您要使用Timer而不是当前线程等),以便我们更好地理解问题。

不管怎样,这里有一个实时读取输出的例子:

final Process proc = new ProcessBuilder(
            "/bin/bash", "-c",
            "for i in `seq 1 10`; do echo $i; sleep $((i % 2)); done")
           .start();
try(InputStreamReader isr = new InputStreamReader(proc.getInputStream())) {
    int c;
    while((c = isr.read()) >= 0) {
        System.out.print((char) c);
        System.out.flush();
    }
}

输出结果:

1         (and wait one second)
2
3         (and wait one second)
4
5         (and wait one second)
6
7         (and wait one second)
8
9         (and wait one second)
10

0

看起来你正在处理多线程问题,而不是获取进程的输出。

我刚刚制作了这个演示类,你可以使用它:

CommandExecTest.java

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;

public class CommandExecTest {
    public static void main(String[] args) throws InterruptedException {

        String executable = "cmd";
        String[] commandParams = {"@ping -n 5 localhost","echo \"hello world\"","exit 123"};
        boolean passCommandsAsLinesToShellExecutableAfterStartup = true;
        AsyncExecutor asyncExecutor = new AsyncExecutor(executable, commandParams,passCommandsAsLinesToShellExecutableAfterStartup);
        System.out.println("x"+"/x\tsecs in main thread \t\t status:"+asyncExecutor.runstate+" of async thread that monitors the process");
        asyncExecutor.start();//start() invokes the run() method as a detached thread
        for(int i=0;i<10;i++) {
            // you can do whatever here and the other process is still running and printing its output inside detached thread
            Thread.sleep(1000);
            System.out.println(i+"/10\tsecs in main thread \t\t status:"+asyncExecutor.runstate+" of async thread that monitors the process");
        }

        asyncExecutor.join(); // main thread has nothing to do anymore, wait till other thread that monitor other process finishes as well
        System.out.println("END OWN-PROGRAMM: 0 , END OTHER PROCESS:"+asyncExecutor.processExitcode);
        System.exit(0);
    }
}

Runstate.java

public static enum Runstate {
    CREATED, RUNNING, STOPPED
}

AsyncExecutor.java

public static class AsyncExecutor extends Thread{
    private String executable;
    private String[] commandParams;
    public ArrayList<String> linesSoFarStdout = new ArrayList<>();
    public ArrayList<String> linesSoFarStderr = new ArrayList<>();
    public Runstate runstate;
    public int processExitcode=-1;
    private boolean passCommandsAsLinesToShellExecutableAfterStartup = false;
    public AsyncExecutor(String executable, String[] commandParams) {
        this.executable=executable;
        this.commandParams=commandParams;
        this.runstate=Runstate.CREATED;
        this.passCommandsAsLinesToShellExecutableAfterStartup=false;
    }
    /**
     * if you want to run a single-process with arguments use <b>false</b> example executable="java" commandParams={"-jar","myjarfile.jar","arg0","arg1"} 
     * <p>
     * if you want to run a shell-process and enter commands afterwards use <b>true</b> example executable="cmd" commandParams={"@ping -n 5 localhost","echo \"hello world\"","exit 123"} 
     * @param executable
     * @param commandParams
     * @param passCommandsAsLinesToShellExecutableAfterStartup
     */
    public AsyncExecutor(String executable, String[] commandParams, boolean passCommandsAsLinesToShellExecutableAfterStartup) {
        this.executable=executable;
        this.commandParams=commandParams;
        this.runstate=Runstate.CREATED;
        this.passCommandsAsLinesToShellExecutableAfterStartup=passCommandsAsLinesToShellExecutableAfterStartup;
    }
    @Override
    public void run() {
        this.runstate=Runstate.RUNNING;
        // 1 start the process
        Process p = null;
        try {
            if(passCommandsAsLinesToShellExecutableAfterStartup) {
                // open a shell-like process like cmd and pass the arguments/command after opening it
                // * example:
                // * open 'cmd' (shell)
                // * write 'echo "hello world"' and press enter
                p = Runtime.getRuntime().exec(new String[] {executable});
                PrintWriter stdin = new PrintWriter( p.getOutputStream());
                for( int i = 0; i < commandParams.length; i++) { 
                    String commandstring = commandParams[i];
                    stdin.println( commandstring);
                }
                stdin.close();
            }
            else {
                // pass the arguments directly during startup to the process
                // * example:
                // * run 'java -jar myexecutable.jar arg0 arg1 ...'
                String[] execWithArgs = new String[commandParams.length+1];
                execWithArgs[0] = executable;
                for(int i=1;i<=commandParams.length;i++) {
                    execWithArgs[i]=commandParams[i-1];
                }
                p = Runtime.getRuntime().exec( execWithArgs);
            }
            // 2 print the output
            InputStream is = p.getInputStream();
            BufferedReader br = new BufferedReader( new InputStreamReader( is));

            InputStream eis = p.getErrorStream();
            BufferedReader ebr = new BufferedReader( new InputStreamReader( eis));

            String lineStdout=null;
            String lineStderr=null;
            while(p.isAlive()) {
                Thread.yield(); // *
                // * free cpu clock for other tasks on your PC! maybe even add thread.sleep(milliseconds) to free some more
                // * everytime this thread gets cpu clock it will try the following codeblock inside the while and yield afterwards for the next time it gets cpu-time from sheduler
                while( (lineStdout = br.readLine()) != null || (lineStderr = ebr.readLine()) != null) {
                    if(lineStdout!=null) {
                        System.out.println(lineStdout);
                        linesSoFarStdout.add(lineStdout);
                    }
                    if(lineStderr!=null) {
                        System.out.println(lineStderr);
                        linesSoFarStderr.add(lineStderr);
                    }
                }
            }
            // 3 when process ends
            this.processExitcode = p.exitValue();
        }
        catch(Exception e) {
            System.err.println("Something went wrong!");
            e.printStackTrace();
        }
        if(processExitcode!=0) {
            System.err.println("The other process stopped with unexpected existcode: " + processExitcode);
        }
        this.runstate=Runstate.STOPPED;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接