Как скопировать потоки ввода / вывода Процесса в их Системные аналоги?
Это продолжение этого вопроса. Предложенный ответ есть
скопировать процессы, ошибки и потоки ввода в версии системы
с IOUtils.copy
следующим образом (после исправления различных ошибок компиляции):
import org.apache.commons.io.IOUtils;
import java.io.IOException;
public class Test {
public static void main(String[] args)
throws IOException, InterruptedException {
final Process process = Runtime.getRuntime().exec("/bin/sh -i");
new Thread(new Runnable() {public void run() {
try {
IOUtils.copy(process.getInputStream(), System.out);
} catch (IOException e) {}
} } ).start();
new Thread(new Runnable() {public void run() {
try {
IOUtils.copy(process.getErrorStream(), System.err);
} catch (IOException e) {}
} } ).start();
new Thread(new Runnable() {public void run() {
try {
IOUtils.copy(System.in, process.getOutputStream());
} catch (IOException e) {}
} } ).start();
process.waitFor();
}
}
Однако полученный код не работает для интерактивных процессов, таких как выполняющийся sh -i
команда. В последнем случае нет ответа ни на один из sh
команды.
Итак, мой вопрос: не могли бы вы предложить альтернативу для копирования потоков, которые будут работать с интерактивными процессами?
3 ответа
Проблема в том, что IOUtil.copy()
работает, пока есть данные в InputStream для копирования. Так как ваш процесс время от времени производит данные, IOUtil.copy()
выходит, так как считает, что нет данных для копирования.
Просто скопируйте данные вручную и используйте логическое значение, чтобы остановить форму потока снаружи:
byte[] buf = new byte[1024];
int len;
while (threadRunning) { // threadRunning is a boolean set outside of your thread
if((len = input.read(buf)) > 0){
output.write(buf, 0, len);
}
}
Он считывает кусками столько байтов, сколько доступно на inputStream, и копирует их все в выходной файл. Внутренне InputStream помещает поток так wait()
и затем будит его, когда данные доступны.
Так что это настолько эффективно, насколько это возможно в этой ситуации.
Process.getOutputStream()
возвращает BufferedOutputStream
, так что если вы хотите, чтобы ваш ввод действительно попал в подпроцесс, вы должны вызвать flush()
после каждого write()
,
Вы также можете переписать свой пример, чтобы сделать все в одном потоке (хотя он использует опрос для одновременного чтения как System.in, так и stdout процесса):
import java.io.*;
public class TestProcessIO {
public static boolean isAlive(Process p) {
try {
p.exitValue();
return false;
}
catch (IllegalThreadStateException e) {
return true;
}
}
public static void main(String[] args) throws IOException {
ProcessBuilder builder = new ProcessBuilder("bash", "-i");
builder.redirectErrorStream(true); // so we can ignore the error stream
Process process = builder.start();
InputStream out = process.getInputStream();
OutputStream in = process.getOutputStream();
byte[] buffer = new byte[4000];
while (isAlive(process)) {
int no = out.available();
if (no > 0) {
int n = out.read(buffer, 0, Math.min(no, buffer.length));
System.out.println(new String(buffer, 0, n));
}
int ni = System.in.available();
if (ni > 0) {
int n = System.in.read(buffer, 0, Math.min(ni, buffer.length));
in.write(buffer, 0, n);
in.flush();
}
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
}
}
System.out.println(process.exitValue());
}
}
Вы должны вместо этого использовать ProcessBuilder.redirectOutput
метод и друзья. Узнайте больше здесь