2010-11-14 11 views
7

To jest kontynuacja do this question. Odpowiedź sugerowane jestJak skopiować strumienie wejściowe/wyjściowe procesu do ich odpowiedników systemowych?

skopiować proces out, err, a strumienie wejściowe do wersji systemu

z IOUtils.copy następująco (po utrwaleniu różne błędy kompilacji):

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(); 
    } 
} 

Jednak wynikowy kod nie działa w przypadku procesów interaktywnych, takich jak wykonujące polecenie sh -i. W tym ostatnim przypadku nie ma odpowiedzi na żadne z poleceń sh.

Moje pytanie brzmi: czy mógłbyś zaproponować alternatywę dla kopiowania strumieni, które będą działać z procesami interaktywnymi?

Odpowiedz

5

Problem polega na tym, że IOUtil.copy() jest uruchomiony, gdy w strumieniu wejściowym znajdują się dane do skopiowania. Ponieważ Twój proces generuje tylko dane od czasu do czasu, IOUtil.copy() wychodzi, ponieważ nie ma danych do skopiowania.

Wystarczy skopiować dane ręcznie i używać logiczną zatrzymać formularz gwint zewnętrzny:

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); 
    } 
} 

ten czyta w kawałki tyle bajtów, ile jest dostępny na InputStream i kopie wszystkich z nich do wyjścia. Wewnętrznie InputStream umieszcza wątek tak wait(), a następnie budzi go, gdy dane są dostępne.
Jest to tak wydajne, jak to możliwe w tej sytuacji.

+1

Dzięki za sugestię. Jednak 'IOUtil.copy()' nie kończy się, dopóki nie zostanie osiągnięty koniec strumienia, więc nie różni się on od twojego kodu, który ma takie same problemy z 'sh'. – vitaut

+0

Pls zapoznaj się z tym http://stackoverflow.com/questions/804951/is-it-possible-to-read-from-a-java-inputstream-with-a-timeout/808795#808795 –

1

Process.getOutputStream() zwraca BufferedOutputStream, więc jeśli chcesz, aby wejście do rzeczywiście dostać się do podproces trzeba zadzwonić flush() po każdym write().

Można również przepisać przykład zrobić wszystko na jednym wątku (choć wykorzystuje odpytywanie czytać zarówno System.in i proces stdout w tym samym czasie):

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

Należy zamiast używania zamówienia ProcessBuilder.redirectOutput metoda & znajomych. Przeczytaj więcej here

Powiązane problemy