Proceso Java con flujo de entrada/salida

Resuelto James moore asked hace 14 años • 3 respuestas

Tengo el siguiente ejemplo de código a continuación. Mediante lo cual puede ingresar un comando en el shell bash, es decir, echo testy hacer que el resultado se repita. Sin embargo, después de la primera lectura. ¿Otros flujos de salida no funcionan?

¿A qué se debe 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 OutputStreamy InputStreamtenga que trabajar en conjunto y no dejar 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);
}
James moore avatar Sep 05 '10 03:09 James moore
Aceptado

En primer lugar, recomendaría reemplazar la línea.

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

con las lineas

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

ProcessBuilder es nuevo en Java 5 y facilita la ejecución de procesos externos. En mi opinión, su mejora más significativa Runtime.getRuntime().exec()es que le permite redirigir el error estándar del proceso hijo a su salida estándar. Esto significa que sólo tienes uno InputStreampara leer. Antes de esto, necesitaba tener dos subprocesos separados, uno leyendo desde stdouty otro leyendo desde stderr, para evitar que el búfer de error estándar se llenara mientras el búfer de salida estándar estaba vacío (lo que provocaba que el proceso secundario se bloqueara), o viceversa.

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

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

solo sale cuando reader, que lee la salida estándar del proceso, devuelve el final del archivo. Esto sólo sucede cuando el bashproceso sale. No devolverá el final del archivo si en este momento no hay más resultados del proceso. En cambio, esperará la siguiente línea de salida del proceso y no regresará hasta tener la siguiente línea.

Dado que estás 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 quedará allí esperando a que se lea otra línea, pero nunca habrá otra línea para leer.

Compilé su código fuente (estoy en Windows en este momento, así que lo reemplacé /bin/bashcon cmd.exe, pero los principios deberían ser los mismos) y encontré que:

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

He visto un truco que hace algo similar a lo que pareces querer, en un programa en el que solía trabajar. Este programa mantuvo varios shells, ejecutó comandos en ellos y leyó el resultado de estos comandos. El truco utilizado fue escribir siempre una línea 'mágica' que marca el final de la salida del comando del shell, y usarla para determinar cuándo había terminado la salida del comando enviado al shell.

Tomé tu código y reemplacé todo después de la línea asignada writercon 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, pude ejecutar de manera confiable algunos comandos y recibir el resultado de cada uno individualmente.

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

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

  • si ingreso un comando que espera la entrada del usuario (por ejemplo, otro shell), el programa parece bloquearse,
  • se supone que cada proceso ejecutado por el shell finaliza su salida con una nueva línea,
  • se vuelve un poco confuso si el comando que ejecuta el shell escribe una línea --EOF--.
  • bashinforma un error de sintaxis y sale si ingresa algún texto con un archivo ).

Es posible que estos puntos no le importen si lo que sea que esté pensando ejecutar como tarea programada se restringirá a un comando o un pequeño conjunto de comandos que nunca se comportarán de manera tan patológica.

EDITAR : mejore el manejo de salida y otros cambios menores después de ejecutar esto en Linux.

Luke Woodward avatar Sep 04 '2010 22:09 Luke Woodward

Creo que puedes usar un hilo como el hilo demoníaco para leer tu entrada y tu lector de salida ya estará en el bucle while del hilo principal para que puedas leer y escribir al mismo tiempo. Puedes modificar tu programa de esta manera:

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 su lector será el mismo que el anterior, es decir

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

haga que su escritor sea final, de lo contrario, la clase interna no podrá acceder a él.

Shashank Jain avatar Feb 02 '2012 18:02 Shashank Jain

Lo tienes writer.close();en tu código. Entonces bash recibe EOF stdiny sale. Luego lo obtienes Broken pipecuando intentas leer desde el stdoutbash desaparecido.

gpeche avatar Sep 04 '2010 22:09 gpeche