Процесс Java с потоком ввода / вывода

91

У меня есть следующий пример кода ниже. Таким образом, вы можете ввести команду в оболочку bash, т. echo testЕ. И вернуть результат. Однако после первого прочтения. Другие выходные потоки не работают?

Почему это или я что-то не так делаю? Моя конечная цель - создать запланированную задачу Threaded, которая периодически выполняет команду для / bash, чтобы OutputStreamи InputStreamдолжны были работать в тандеме и не переставать работать. У меня тоже возникла ошибка. Какие- java.io.IOException: Broken pipeнибудь идеи?

Благодарю.

String line;
Scanner scan = new Scanner(System.in);

Process process = Runtime.getRuntime ().exec ("/bin/bash");
OutputStream stdin = process.getOutputStream ();
InputStream stderr = process.getErrorStream ();
InputStream stdout = process.getInputStream ();

BufferedReader reader = new BufferedReader (new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

String input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.close();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}
Джеймс Мур
источник
«Сломанная труба», вероятно, означает, что дочерний процесс завершился. Еще не изучил остальную часть кода, чтобы узнать, в чем еще проблемы.
vanza
1
используйте отдельные потоки, он будет работать нормально
Johnydep

Ответы:

140

Во-первых, я бы рекомендовал заменить строчку

Process process = Runtime.getRuntime ().exec ("/bin/bash");

с линиями

ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();

ProcessBuilder - новинка Java 5, которая упрощает выполнение внешних процессов. На мой взгляд, его наиболее значительным улучшением Runtime.getRuntime().exec()является то, что он позволяет перенаправлять стандартную ошибку дочернего процесса в его стандартный вывод. Это означает, что вам нужно InputStreamчитать только один . До этого вам нужно было иметь два отдельных потока, один для чтения stdoutи один для чтения stderr, чтобы избежать заполнения стандартного буфера ошибок, когда стандартный выходной буфер был пуст (что приводило к зависанию дочернего процесса), или наоборот.

Далее петли (их у вас две)

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

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

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

Я скомпилировал ваш исходный код (сейчас я использую Windows, поэтому я заменил его /bin/bashна cmd.exe, но принципы должны быть такими же), и я обнаружил, что:

  • после ввода в две строки появляется вывод первых двух команд, но затем программа зависает,
  • если я ввожу, скажем,,, echo testа затем exit, программа выходит из первого цикла с момента cmd.exeвыхода из процесса. Затем программа запрашивает другую строку ввода (которая игнорируется), сразу пропускает второй цикл, поскольку дочерний процесс уже завершился, а затем завершает работу.
  • если я наберу, exitа затем echo test, я получаю исключение IOException с жалобой на закрытие канала. Этого и следовало ожидать - первая строка ввода привела к завершению процесса, а вторую строку отправить некуда.

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

Я взял ваш код и заменил все после строки, которая writerотносится к следующему циклу:

while (scan.hasNext()) {
    String input = scan.nextLine();
    if (input.trim().equals("exit")) {
        // Putting 'exit' amongst the echo --EOF--s below doesn't work.
        writer.write("exit\n");
    } else {
        writer.write("((" + input + ") && echo --EOF--) || echo --EOF--\n");
    }
    writer.flush();

    line = reader.readLine();
    while (line != null && ! line.trim().equals("--EOF--")) {
        System.out.println ("Stdout: " + line);
        line = reader.readLine();
    }
    if (line == null) {
        break;
    }
}

После этого я мог надежно запустить несколько команд, и результат каждой из них возвращался мне индивидуально.

Две echo --EOF--команды в строке, отправленной в оболочку, предназначены для того, чтобы гарантировать, что вывод команды завершается --EOF--даже в результате ошибки команды.

Конечно, у этого подхода есть свои ограничения. Эти ограничения включают:

  • если я ввожу команду, ожидающую ввода пользователя (например, другую оболочку), программа, кажется, зависает,
  • предполагается, что каждый процесс, выполняемый оболочкой, завершает свой вывод новой строкой,
  • он немного сбивается с толку, если команда, выполняемая оболочкой, записывает строку --EOF--.
  • bashсообщает о синтаксической ошибке и завершает работу, если вы вводите текст с несоответствующим ).

Эти моменты могут не иметь для вас значения, если то, что вы думаете о запуске в качестве запланированной задачи, будет ограничено командой или небольшим набором команд, которые никогда не будут вести себя таким патологическим образом.

РЕДАКТИРОВАТЬ : улучшить обработку выхода и другие незначительные изменения после запуска этого в Linux.

Люк Вудворд
источник
Спасибо за исчерпывающий ответ. Однако я думаю, что определил истинную причину своих проблем. Доводил до моего сведения в своем посте. stackoverflow.com/questions/3645889/… . Спасибо.
Джеймс Мур
1
замена / bin / bash на cmd на самом деле не вела себя так же, как я сделал программу, которая делала то же самое, но имела проблемы с bash. В моем случае открытие трех отдельных потоков для каждого ввода / вывода / ошибки лучше всего без проблем работает для интерактивных команд длительного сеанса.
Johnydep
stackoverflow.com/questions/14765828/…
Александр Миллс
@AlexMills: ваш второй комментарий означает, что вы решили свою проблему? В вашем первом комментарии нигде нет подробностей, чтобы сказать, в чем проблема, или почему вы «внезапно» получаете исключения.
Люк Вудворд
@Luke, ссылка, которую я предоставил чуть выше вашего комментария, решает мою проблему
Александр Миллс
4

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

Thread T=new Thread(new Runnable() {

    @Override
    public void run() {
        while(true)
        {
            String input = scan.nextLine();
            input += "\n";
            try {
                writer.write(input);
                writer.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }
} );
T.start();

и вы можете читатель будет таким же, как указано выше, т.е.

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

сделайте свой писатель окончательным, иначе он не будет доступен внутреннему классу.

Шашанк Джайн
источник
1

У вас есть writer.close();в вашем коде. Итак, bash получает EOF stdinи закрывается. Тогда вы получите Broken pipeпри попытке прочитать из stdoutнесуществующего bash.

gpeche
источник