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