По-перше, я б рекомендував замінити рядок
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.