How to use ProcessBuilder to continually interact with a CLI program

懵懂的女人 提交于 2020-01-30 13:15:31

问题


I use a CLI program regularly which is accessed through a docker container. Once I enter the container, I can start using my CLI program in question. The issue I'm having is I want to continue to interact with the same command line instance. Basically I'm trying to create a GUI program that will run "on top" of a CLI program. I just don't know how to keep sending commands to the same CLI instance:

List<String> command = new ArrayList<String>();
command.add("cmd.exe" );
command.add("/c");
command.add("docker-compose up -d");
System.out.println(command);

ProcessBuilder builder = new ProcessBuilder(command);
builder.inheritIO();
Map<String, String> environ = builder.environment();

Process process = builder.start();
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
    System.out.println(line);
}
command.clear();
command.add("cmd.exe" );
command.add("/c");
command.add("docker ps");
System.out.println(command);

process = builder.start();
is = process.getInputStream();
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
while ((line = br.readLine()) != null) {
    System.out.println(line);
}

But this isn't working the way I would like it to. Above, you'll see I'm running two commands: docker-compose up -d and then docker ps. But I don't think they are running in the same instance. So if I were to change the directory in the first command, it's not going to remember the directory for the second command.

Also, it seems to be running my commands in reverse order from the order in the code.


回答1:


Instances of class ProcessBuilder are intended to be short-lived, in my opinion. I don't think creating a new instance each time you want to create a new process wastes memory or other resources - but I'm only guessing.

In any case, to re-use a ProcessBuilder instance in order to execute several processes, you simply use its methods, like command(String...)

I wrote a small Swing app that lets the user enter a [operating system] command and displays that command's output. It's not production ready, but I hope it's enough to get you going.

Note that creating and handling a Process in java code is not simple nor intuitive. The article When Runtime.exec() won't helped me a lot. It is an ancient article, but nonetheless still relevant (again, in my opinion). Simply replace references to class Runtime in the article with class ProcessBuilder since the article was written before ProcessBuilder was added to the JDK.

Here is the code of my app. Please refer to the above-mentioned article in order to understand the ProcessBuilder related code. In order to understand the Swing code, I recommend the tutorial Creating a GUI With JFC/Swing

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class ProcExec implements ActionListener, Runnable {
    private static final String CLEAR = "Clear";
    private static final String EXIT = "Exit";
    private static final String RUN = "Run";

    private JTextArea commandOutput;
    private JTextArea textArea;
    private ProcessBuilder procBuilder;

    public ProcExec() {
        procBuilder = new ProcessBuilder();
    }

    public void actionPerformed(ActionEvent actionEvent) {
        String actionCommand = actionEvent.getActionCommand();
        if (CLEAR.equals(actionCommand)) {
            textArea.setText("");
        }
        else if (EXIT.equals(actionCommand)) {
            System.exit(0);
        }
        else if (RUN.equals(actionCommand)) {
            try {
                execute();
            }
            catch (Exception x) {
                x.printStackTrace();
            }
        }
    }

    public void run() {
        createAndDisplayGui();
    }

    private void createAndDisplayGui() {
        JFrame frame = new JFrame("Process Executor");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.add(createTopPanel(), BorderLayout.PAGE_START);
        frame.add(createCommandPanel(), BorderLayout.CENTER);
        frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private JButton createButton(String text, int mnemonic, String tooltip) {
        JButton button = new JButton(text);
        button.setMnemonic(mnemonic);
        button.setToolTipText(tooltip);
        button.addActionListener(this);
        return button;
    }

    private JPanel createButtonsPanel() {
        JPanel buttonsPanel = new JPanel();
        buttonsPanel.add(createButton(RUN, KeyEvent.VK_R, "Run entered command."));
        buttonsPanel.add(createButton(CLEAR, KeyEvent.VK_C, "Removes entered command."));
        buttonsPanel.add(createButton(EXIT, KeyEvent.VK_X, "Exit application."));
        return buttonsPanel;
    }

    private JSplitPane createCommandPanel() {
        textArea = new JTextArea(30, 40);
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        JScrollPane cmdScrollPane = new JScrollPane(textArea);
        commandOutput = new JTextArea(30, 80);
        JScrollPane outputScrollPane = new JScrollPane(commandOutput);
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                                              cmdScrollPane,
                                              outputScrollPane);
        return splitPane;
    }

    private JPanel createTopPanel() {
        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        JLabel label = new JLabel("Enter a command...");
        topPanel.add(label);
        return topPanel;
    }

    private int execute() throws IOException, InterruptedException {
        commandOutput.setText("");
        String raw = textArea.getText();
        String[] words = raw.split(" ");
        String[] command = new String[words.length + 2];
        command[0] = "cmd.exe";
        command[1] = "/C";
        System.arraycopy(words, 0, command, 2, words.length);
        procBuilder.command(command);
        Process proc = procBuilder.start();
        ProcHandler stdout = new ProcHandler(proc.getInputStream());
        ProcHandler stderr = new ProcHandler(proc.getErrorStream());
        Thread stdoutThread = new Thread(stdout);
        stdoutThread.start();
        Thread stderrThread = new Thread(stderr);
        stderrThread.start();
        int status = proc.waitFor();
        stderrThread.join();
        stdoutThread.join();
        return status;
    }

    private class ProcHandler implements Runnable {
        private BufferedReader streamReader;

        public ProcHandler(InputStream is) {
            InputStreamReader isr = new InputStreamReader(is);
            streamReader = new BufferedReader(isr);
        }

        public void run() {
            try {
                String line = streamReader.readLine();
                while (line != null) {
                    SwingUtilities.invokeLater(new StreamLine(line));
                    line = streamReader.readLine();
                }
            }
            catch (Exception x) {
                throw new RuntimeException("Stream reading failed.", x);
            }
        }
    }

    private class StreamLine implements Runnable {
        private final String text;

        public StreamLine(String txt) {
            text = txt + "\n";
        }

        public void run() {
            ProcExec.this.commandOutput.append(text);
        }
    }

    public static void main(String[] args) {
        ProcExec instance = new ProcExec();
        EventQueue.invokeLater(instance);
    }
}


来源:https://stackoverflow.com/questions/58294711/how-to-use-processbuilder-to-continually-interact-with-a-cli-program

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