Java-процесс с потоком ввода / вывода
У меня есть следующий пример кода ниже. Тем самым вы можете ввести команду в оболочку bash, т.е. echo test
и получить результат echo'd обратно. Однако после первого прочтения. Другие выходные потоки не работают?
Почему это или я что-то не так делаю? Моя конечная цель - создать запланированную многопоточную задачу, которая периодически выполняет команду для /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);
}
3 ответа
Во-первых, я бы порекомендовал заменить линию
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.
Я думаю, что вы можете использовать нить типа demon-thread для чтения ваших входных данных, и ваш читатель вывода уже будет в цикле 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);
}
сделайте вашего писателя окончательным, иначе он не будет доступен для внутреннего класса.
У тебя есть writer.close();
в вашем коде. Так Баш получает EOF на свой stdin
и выходит. Тогда вы получите Broken pipe
при попытке прочитать из stdout
несуществующей баш.