Во-первых, я бы рекомендовал заменить строчку
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")) {
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.