2010-09-04 13 views
81

Tengo el siguiente ejemplo de código a continuación. Por medio de lo cual puede ingresar un comando al shell bash, es decir, echo test y recuperar el resultado. Sin embargo, después de la primera lectura. ¿Otras transmisiones de salida no funcionan?Proceso de Java con flujo de entrada/salida

¿Por qué es esto o estoy haciendo algo mal? Mi objetivo final es crear una tarea programada con subprocesos que ejecute un comando periódicamente en/bash para que OutputStream y InputStream tengan que funcionar en tándem y no dejen de funcionar. También he estado experimentando el error java.io.IOException: Broken pipe alguna idea?

Gracias.

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

"Tubo roto" probablemente significa que el proceso hijo ha salido. No he revisado completamente el resto de su código para ver cuáles son los otros problemas. – vanza

+1

use hilos separados, funcionará muy bien – Johnydep

Respuesta

123

En primer lugar, me gustaría recomendar el reemplazar la línea

Process process = Runtime.getRuntime().exec ("/bin/bash"); 

con las líneas

ProcessBuilder builder = new ProcessBuilder("/bin/bash"); 
builder.redirectErrorStream(true); 
Process process = builder.start(); 

ProcessBuilder es nuevo en Java 5 y hace correr procesos externos más fácil. En mi opinión, su mejora más significativa sobre Runtime.getRuntime().exec() es que le permite redirigir el error estándar del proceso hijo a su salida estándar. Esto significa que solo tiene un InputStream para leer. Antes de esto, tenía que tener dos hilos separados, uno leyendo stdout y uno leyendo stderr, para evitar el llenado estándar del búfer de error mientras el búfer de salida estándar estaba vacío (lo que provocaba que el proceso hijo se bloquee), o viceversa.

A continuación, los bucles (de los cuales tiene dos)

while ((line = reader.readLine()) != null) { 
    System.out.println ("Stdout: " + line); 
} 

única salida cuando el reader, que se lee de salida estándar del proceso, devuelve al final de su archivo. Esto solo ocurre cuando el proceso finaliza. No devolverá el final del archivo si sucede en este momento que no habrá más resultados del proceso. En cambio, esperará la siguiente línea de salida del proceso y no regresará hasta que tenga la siguiente línea.

Dado que está enviando dos líneas de entrada al proceso antes de llegar a este bucle, el primero de estos dos bucles se bloqueará si el proceso no ha salido después de estas dos líneas de entrada. Se sentará allí esperando a que se lea otra línea, pero nunca habrá otra línea para leer.

He compilado el código fuente (estoy en Windows por el momento, así que sustituye /bin/bash con cmd.exe, pero los principios deben ser los mismos), y me encontré con que:

  • después de escribir en dos líneas, aparece el resultado de los primeros dos comandos, pero luego el programa se bloquea,
  • si escribo, por ejemplo, echo test, y luego exit, el programa sale del primer ciclo desde que salió el proceso cmd.exe. Luego, el programa solicita otra línea de entrada (que se ignora), salta directamente sobre el segundo ciclo ya que el proceso hijo ya salió, y luego sale solo.
  • si escribo exit y luego echo test, recibo una IOException quejándose de que se cerró una tubería. Esto es de esperar: la primera línea de entrada provocó que el proceso salga y no hay ningún lugar para enviar la segunda línea.

He visto un truco que hace algo similar a lo que parece querer, en un programa en el que solía trabajar. Este programa mantuvo alrededor de una cantidad de proyectiles, ejecutó comandos en ellos y leyó los resultados de estos comandos. El truco utilizado fue escribir siempre una línea "mágica" que marca el final de la salida del comando de shell, y usar eso para determinar cuándo la salida del comando enviado al shell había finalizado.

Tomé su código y que sustituyen todo después de la línea que asigna a writer con el siguiente bucle:

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

Después de hacer esto, podría funcionar de forma fiable unas pocas órdenes y tienen la salida de cada uno volver para mí individualmente

Los dos comandos echo --EOF-- en la línea enviada al shell están allí para garantizar que la salida del comando finalice con --EOF-- incluso en el resultado de un error del comando.

Por supuesto, este enfoque tiene sus limitaciones. Estas limitaciones incluyen:

  • si entro en un comando que espera la entrada del usuario (por ejemplo, otro shell), el programa parece bloquearse,
  • asume que cada proceso dirigido por la cáscara termina su salida con un salto de línea ,
  • se confunde un poco si el comando que ejecuta el shell pasa a escribir una línea --EOF--.
  • bash informa un error de sintaxis y se cierra si ingresa texto con un ) sin igual.

Estos puntos pueden no importarle si lo que está pensando ejecutar como una tarea programada va a estar restringido a un comando o un pequeño conjunto de comandos que nunca se comportarán de manera tan patológica.

EDIT: mejora el manejo de salida y otros pequeños cambios después de ejecutar esto en Linux.

+0

Gracias por la respuesta completa Sin embargo, creo que he identificado el verdadero caso para mis problemas. Me llamó la atención en tu publicación. http: // stackoverflow.com/questions/3645889/java-dealing-with-child-process. Gracias. –

+1

reemplazando/bin/bash con cmd en realidad no se comportaba igual, como hice el programa que hizo lo mismo pero tuvo problemas con bash. En mycase, abrir tres subprocesos separados para cada entrada/salida/error funciona mejor sin ningún problema para los comandos interactivos de sesión larga. – Johnydep

+0

http://stackoverflow.com/questions/14765828/processbuilder-giving-a-file-not-found-exception-when-the-file-does-exist –

1

Usted tiene writer.close(); en su código. Entonces bash recibe EOF en su stdin y sale. Luego obtienes Broken pipe cuando tratas de leer del stdout del bash difunto.

4

Creo que puede usar hilo como demonio para leer su entrada y su lector de salida ya estará en el ciclo while en el hilo principal para que pueda leer y escribir al mismo tiempo. Puede modificar su programa así:

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

y puedes lector será igual que el anterior, es decir

while ((line = reader.readLine()) != null) { 
    System.out.println ("Stdout: " + line); 
} 

hacer que su escritor como definitiva de lo contrario no será capaz de acceder en clase interna.

Cuestiones relacionadas