0

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
  • Two processes are two processes. Not 'one instance'. Given this code, they run in the order of this code, but what you will get is two instances of the first command. If you want the use the second version of command you have tonier a new builder. Commented Oct 9, 2019 at 2:53

1 Answer 1

2

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);
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for this, I'm going to run through this in my head as I am in the process of relearning Java after a long break of almost 10 years. A few questions, when I try to compile this, it's saying the method ProcExec() has no return type. So when I make it a void method, it says it can't find the symbol ProcExec() (it's referenced in the StreamLine class later on). When I specify a return type of ProcessBuilder in ProcExec(), it's giving me the same error, cannot find symbol. Is there some reason that the StreamLine class can't access the ProcExec() method?
It's having an issue with ProcExec.this.commandOutput.append(text);
@SultanofSwing ProcExec() is a constructor and hence has no return type. My code is a SSCCE. You just need to copy it as is. It should compile and run. If you changed my code then it may no longer be compilable nor runnable.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.