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

后端 未结 2 1136
有刺的猬
有刺的猬 2020-12-06 06:47

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

相关标签:
2条回答
  • 2020-12-06 07:26

    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.

    0 讨论(0)
  • 2020-12-06 07:28

    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.

    0 讨论(0)
提交回复
热议问题