Во-первых, я бы порекомендовал заменить строку
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
сообщает о синтаксической ошибке и завершается, если вы вводите какой-либо текст с несогласованным )
.
Эти пункты могут не иметь значения для вас, если все, что вы думаете о выполнении в качестве запланированной задачи, будет ограничено командой или небольшим набором команд, которые никогда не будут вести себя таким патологическим образом.
EDIT : улучшена обработка выхода и другие незначительные изменения после запуска этого в Linux.