Как скопировать потоки ввода / вывода Процесса в их Системные аналоги? - PullRequest
7 голосов
/ 14 ноября 2010

Это продолжение до этого вопроса . Предполагаемый ответ:

для копирования потоков процесса, ошибок и ввода в версии системы

с 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 ]

5 голосов
/ 14 ноября 2010

Проблема в том, что 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() и затем пробуждает его, когда данные доступны.
Так что это настолько эффективно, насколько это возможно в этой ситуации.

2 голосов
/ 12 марта 2011

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());
  }
}
1 голос
/ 28 июля 2013

Вместо этого вы должны использовать метод ProcessBuilder.redirectOutput и друзья.Читать дальше здесь

...