Как использовать ProcessBuilder для постоянного взаимодействия с программой CLI - PullRequest
0 голосов
/ 09 октября 2019

Я регулярно использую программу CLI, доступ к которой осуществляется через контейнер Docker. Как только я войду в контейнер, я могу начать использовать свою программу CLI. У меня проблема в том, что я хочу продолжить взаимодействие с тем же экземпляром командной строки. В основном я пытаюсь создать программу с графическим интерфейсом, которая будет работать "поверх" программы CLI. Я просто не знаю, как продолжать посылать команды одному и тому же экземпляру CLI:

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);
}

Но это не работает так, как мне бы хотелось. Выше вы увидите, что я запускаю две команды: docker-compose up -d и затем docker ps. Но я не думаю, что они работают в одном экземпляре. Поэтому, если бы я изменил каталог в первой команде, он не запомнит каталог для второй команды.

Кроме того, похоже, что мои команды выполняются в порядке, обратном порядку в коде.

1 Ответ

1 голос
/ 09 октября 2019

Экземпляры класса ProcessBuilder должны быть недолговечными, на мой взгляд. Я не думаю, что создание нового экземпляра каждый раз, когда вы хотите создать новый процесс, тратит впустую память или другие ресурсы - но я только предполагаю.

В любом случае, повторно использовать экземпляр ProcessBuilderчтобы выполнить несколько процессов, вы просто используете его методы, такие как command(String...)

Я написал небольшое приложение Swing , которое позволяет пользователю войти в [операционную систему] и отображает вывод этой команды. Это не производство готово, но я надеюсь, что этого достаточно, чтобы вы начали.

Обратите внимание, что создание и обработка Process в Java-коде не просты и не интуитивны,Статья Когда Runtime.exec () не мне очень помогла. Это древняя статья, но тем не менее актуальная (опять же, на мой взгляд). Просто замените ссылки на класс Runtime в статье на класс ProcessBuilder, поскольку статья была написана до добавления ProcessBuilder в JDK.

Вот код моего приложения. Пожалуйста, обратитесь к вышеупомянутой статье, чтобы понять связанный код ProcessBuilder. Чтобы понять код Swing , я рекомендую учебник Создание графического интерфейса с 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);
    }
}
...