Терминал Java печатает только вывод из первой команды

РЕДАКТИРОВАТЬ: теперь код работает! Вот как я это сделал:

package me.nrubin29.jterminal;

import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    private File workingFolder = FileSystemView.getFileSystemView().getDefaultDirectory();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                        Process bash = new ProcessBuilder("bash").directory(workingFolder).start();

                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        int code = bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                        if (code == 0 && command.split(" ").length > 1) workingFolder = new File(command.split(" ")[1]);

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            if (lines.length == 0) return;
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}

Я работаю над терминальным приложением Java. Он работает, за исключением того, что он печатает только вывод первой команды. Вот изображение графического интерфейса, когда я пытаюсь запустить ls более одного раза.

JTerminal GUI

package me.nrubin29.jterminal;

import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        final Process bash = new ProcessBuilder("/bin/bash").start();

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}

person nrubin29    schedule 27.08.2013    source источник
comment
Хотел проверить это, но, видимо, это не работает на Mac. Вы пытались отладить его, чтобы увидеть, где теряются данные?   -  person GGrec    schedule 28.08.2013
comment
Ну ладно, тогда у меня могут отсутствовать JAR. Идите вперед и отлаживайте его.   -  person GGrec    schedule 28.08.2013
comment
@GGrec Должен работать на Mac, протестировал свое решение с помощью одного...   -  person MadProgrammer    schedule 28.08.2013


Ответы (2)


Объект Process можно использовать только один раз, поэтому последующие вызовы Process.waitFor() сразу возвращаются (поскольку процесс уже завершен). Вместо этого вам нужно каждый раз запрашивать новый процесс у вашего ProcessBuilder.

Вот правильный код:

package me.nrubin29.jterminal;
import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.*;
import java.util.ArrayList;

public class JTerminal extends JFrame {

    private JTextPane area = new JTextPane();
    private JTextField input = new JTextField("Input");

    private SimpleAttributeSet inputSAS = new SimpleAttributeSet(), output = new SimpleAttributeSet(), error = new SimpleAttributeSet();

    public JTerminal() throws IOException {
        super("JTerminal");

        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        StyleConstants.setForeground(inputSAS, Color.GREEN);
        StyleConstants.setBackground(inputSAS, Color.BLACK);

        StyleConstants.setForeground(output, Color.WHITE);
        StyleConstants.setBackground(output, Color.BLACK);

        StyleConstants.setForeground(error, Color.RED);
        StyleConstants.setBackground(error, Color.BLACK);

        final ProcessBuilder builder = new ProcessBuilder("/bin/bash");

        input.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    try {
                        String command = input.getText();
                        if (command.equals("")) return;

                        setTitle("JTerminal (" + command.split(" ")[0] + ")");

                        input.setText("");
                        input.setEditable(false);

                        write(inputSAS, command);

                                Process bash = builder.start();
                        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bash.getOutputStream());
                        outputStreamWriter.write(command);
                        outputStreamWriter.close();

                        bash.waitFor();

                        writeStream(bash.getErrorStream(), error);
                        writeStream(bash.getInputStream(), output);

                        input.setEditable(true);
                        setTitle("JTerminal");

                    } catch (Exception ex) { error(ex); }
                }
            }

            public void keyTyped(KeyEvent e) {}
            public void keyReleased(KeyEvent e) {}
        });

        area.setBackground(Color.black);
        area.setCaretColor(Color.green);
        area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        area.setEditable(false);

        JScrollPane pane = new JScrollPane(area);
        pane.setBorder(BorderFactory.createLineBorder(Color.GREEN));
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        pane.setPreferredSize(new Dimension(640, 460));

        input.setBackground(Color.black);
        input.setForeground(Color.green);
        input.setCaretColor(Color.green);
        input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
        input.setBorder(BorderFactory.createLineBorder(Color.GREEN));

        add(pane);
        add(input);

        Dimension DIM = new Dimension(640, 480);
        setPreferredSize(DIM);
        setSize(DIM);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(true);
        pack();
        setVisible(true);

        input.requestFocus();
    }

    public static void main(String[] args) throws IOException {
        new JTerminal();
    }

    private void write(SimpleAttributeSet attributeSet, String... lines) {
        try {
            area.getStyledDocument().insertString(area.getStyledDocument().getLength(), "\n", attributeSet);
            for (String line : lines) {
                area.getStyledDocument().insertString(area.getStyledDocument().getLength(), line + "\n", attributeSet);
            }
        }
        catch (Exception e) { error(e); }
    }

    private void error(Exception e) {
        write(error, "An error has occured: " + e.getLocalizedMessage());
        e.printStackTrace(); //TODO: temp.
    }

    private void writeStream(InputStream s, SimpleAttributeSet color) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(s));

            ArrayList<String> strs = new ArrayList<String>();

            while(reader.ready()) strs.add(reader.readLine());

            if (strs.size() > 0) write(color, strs.toArray(new String[strs.size()]));
        }
        catch (Exception e) { error(e); }
    }
}
person Menthos    schedule 27.08.2013
comment
Или вы можете установить рабочий каталог ProcessBuilder после каждой команды: docs.oracle.com/javase/6/docs/api/java/lang/ - person Menthos; 28.08.2013
comment
На правильном пути, но я бы беспокоился о bash.waitFor, как только он вернется, это будет означать, что процесс завершился. Кроме того, waitingFor следует выполнять ПОСЛЕ чтения ввода процесса... - person MadProgrammer; 28.08.2013
comment
@Menthos Или вы можете установить рабочий каталог ProcessBuilder после каждой команды: - все равно потребуется создавать новый Process для каждой новой команды, что противоречит цели... - person MadProgrammer; 28.08.2013
comment
@MadProgrammer Кроме того, waitFor следует выполнять ПОСЛЕ чтения ввода процесса ... Да, это правда, особенно потому, что вы не можете полагаться на буферы выходного потока, которые зависят от системы. С другой стороны, правильное обращение с этим выходит за рамки вопросов :) - person Menthos; 28.08.2013
comment
@Menthos Возможно, но комментарий в вашем ответе может помочь ОП вернуться к вам и спросить, почему что-то еще не работает;) - person MadProgrammer; 28.08.2013

Как только процесс завершился, он не может быть «прочитан» или «записан» в него.

Ваш код также будет блокировать метод «waitFor», что означает, что ваш пользовательский интерфейс не будет обновляться до тех пор, пока процесс не завершится. Это действительно полезно...

Вместо этого вам нужно запустить процесс и позволить ему продолжать работу, наблюдая за состоянием в фоновом режиме.

Поскольку Swing представляет собой однопоточную среду, вам нужно быть осторожным в отношении того, как вы обрабатываете длительные/блокирующие процессы и обновления пользовательского интерфейса.

С этой целью я использовал SwingWorker для чтения вывода Process в фоновом потоке и повторной синхронизации обновлений с потоком диспетчеризации событий. Это позволяет пользовательскому интерфейсу продолжать работать и оставаться отзывчивым, пока выходные данные запущенного процесса считываются в...

Кроме того, вместо того, чтобы каждый раз «запускать» новую команду, создавая новый процесс, вам нужно будет писать во входной поток ввода Process.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestTerminal {

    public static void main(String[] args) {
        new TestTerminal();
    }

    public TestTerminal() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JTextArea output;
        private JTextField input;

        private Process process;

        public TestPane() {
            setLayout(new BorderLayout());

            output = new JTextArea(20, 20);
            input = new JTextField(10);

            output.setLineWrap(false);
            output.setWrapStyleWord(false);
            output.setEditable(false);
            output.setFocusable(false);

            add(new JScrollPane(output));
            add(input, BorderLayout.SOUTH);

            input.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    String cmd = input.getText() + "\n";
                    input.setText(null);
                    output.append("\n" + cmd + "\n\n");
                    if (process == null) {
                        ProcessBuilder pb = new ProcessBuilder("bash");
                        pb.directory(new File("."));
                        try {
                            process = pb.start();
                            InputStreamWorker isw = new InputStreamWorker(output, process.getInputStream());
                            isw.execute();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                            input.setEnabled(false);
                        }

                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                int exit = -1;
                                try {
                                    exit = process.waitFor();
                                } catch (InterruptedException ex) {
                                }
                                System.out.println("Exited with " + exit);
                                input.setEnabled(false);
                            }
                        }).start();

                    }
                    OutputStream os = process.getOutputStream();
                    try {
                        os.write(cmd.getBytes());
                        os.flush();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                        input.setEnabled(false);
                    }
                }
            });
        }

    }

    public class InputStreamWorker extends SwingWorker<Void, Character> {

        private InputStream is;
        private JTextArea output;

        public InputStreamWorker(JTextArea output, InputStream is) {
            this.is = is;
            this.output = output;
        }

        @Override
        protected void process(List<Character> chunks) {
            StringBuilder sb = new StringBuilder(chunks.size());
            for (Character c : chunks) {
                sb.append(c);
            }
            output.append(sb.toString());
        }

        @Override
        protected Void doInBackground() throws Exception {
            int in = -1;
            while ((in = is.read()) != -1) {
                publish((char)in);
            }
            return null;
        }

    }

}

Я бы также рекомендовал вам избегать KeyListener там, где это возможно. JTextField использует ActionListener, который будет вызываться всякий раз, когда пользователь нажимает клавишу «действие», что бы это ни было для данной платформы...

person MadProgrammer    schedule 27.08.2013