Run a sub process, provide input and output to it correctly in Java

纵然是瞬间 提交于 2019-12-28 06:22:11

问题


I use Runtime exec() method to create a subprocess in Java. However, since the subprocess is an interactive program, I need to provide input to it as and when required by it. Also I need to show the output of the subprocess. How can I do this in the simplest possible way?

I was using a StreamGobbler to show the program output using process.getInputStream(). I, however, do not know how to identify when the program is waiting for input and when to provide it input using proc.getOutputStream. How can I do this?


回答1:


You need to copy the input and output between the subprocess' streams and System streams (System.in, System.out and System.err). This is related to my recent quesion. The best solution I have found so far is:

import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.FileChannel;

class StreamCopier implements Runnable {
    private InputStream in;
    private OutputStream out;

    public StreamCopier(InputStream in, OutputStream out) {
        this.in = in;
        this.out = out;
    }

    public void run() {
        try {
            int n;
            byte[] buffer = new byte[4096];
            while ((n = in.read(buffer)) != -1) {
                out.write(buffer, 0, n);
                out.flush();
            }
        }
        catch (IOException e) {
            System.out.println(e);
        }
    }
}

class InputCopier implements Runnable {
    private FileChannel in;
    private OutputStream out;

    public InputCopier(FileChannel in, OutputStream out) {
        this.in = in;
        this.out = out;
    }

    public void run() {
        try {
            int n;
            ByteBuffer buffer = ByteBuffer.allocate(4096);
            while ((n = in.read(buffer)) != -1) {
                out.write(buffer.array(), 0, n);
                out.flush();
            }
            out.close();
        }
        catch (AsynchronousCloseException e) {}
        catch (IOException e) {
            System.out.println(e);
        }
    }
}

public class Test {
    private static FileChannel getChannel(InputStream in)
            throws NoSuchFieldException, IllegalAccessException {
        Field f = FilterInputStream.class.getDeclaredField("in");
        f.setAccessible(true);
        while (in instanceof FilterInputStream)
            in = (InputStream)f.get((FilterInputStream)in);
        return ((FileInputStream)in).getChannel();
    }

    public static void main(String[] args)
            throws IOException, InterruptedException,
                   NoSuchFieldException, IllegalAccessException {
        Process process = Runtime.getRuntime().exec("sh -i +m");
        Thread outThread = new Thread(new StreamCopier(
                process.getInputStream(), System.out));
        outThread.start();
        Thread errThread = new Thread(new StreamCopier(
                process.getErrorStream(), System.err));
        errThread.start();
        Thread inThread = new Thread(new InputCopier(
                getChannel(System.in), process.getOutputStream()));
        inThread.start();
        process.waitFor();
        System.in.close();
        outThread.join();
        errThread.join();
        inThread.join();
    }
}

The tricky part here is to extract a channel from System.in. Without this you will not be able to interrupt the thread that reads input when the subprocess terminates.

This approach has a serious drawback: after closing System.in you can no longer read from it. The workaround that I'm currently using is to have a single input redirecting thread used for all subprocesses.




回答2:


Ask yourself "How do I know when the program wants input when I run it from the command line"? You see what it prompts and enter data based on that prompt. The principle will be the same, except your code will need to interpret the program's output and provide the correct input.

To avoid reinventing the wheel, take a look at ExpectJ and/or Expect4J, which are Java implementations of the venerable *nix Expect tool, which is designed to handle this kind of programmatic interaction.



来源:https://stackoverflow.com/questions/4272956/run-a-sub-process-provide-input-and-output-to-it-correctly-in-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!