2009-05-19 16 views
50

Al experimentar problemas de red en máquinas cliente, me gustaría poder ejecutar algunas líneas de comando y enviarme los resultados por correo electrónico.Captura de stdout al llamar a Runtime.exec

He encontrado Runtime.exec que me permitirá ejecutar comandos arbitrarios, pero Recolectar los resultados en una cadena es más interesante.

Me doy cuenta de que podría redirigir la salida a un archivo, y luego leer desde el archivo, pero mi sentido spidey me dice que hay una forma más elegante de hacerlo.

Sugerencias?

+0

Eche un vistazo a este [artículo] (http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html). – kgiannakakis

Respuesta

46

Debe capturar tanto el estándar como el estándar en el proceso. A continuación, puede escribir de forma estándar en un archivo/correo o similar.

Ver this article para obtener más información, y en particular el mecanismo de nota StreamGobbler que captura la salida estándar/err en hilos separados. ¡Esto es esencial para evitar el bloqueo y es la fuente de numerosos errores si no lo haces correctamente!

+0

Observé que si el comando da muchos resultados, el flujo de código continuará antes de que los Gobblers terminen de generar. Necesito llamar a .join() sobre ellos antes de regresar. – Zitrax

+0

_Extremely_ método efectivo y simple. Una cosa a tener en cuenta es que la inicialización de la matriz 'cmd' en el método Main parece estar un poco anticuada para Windows 7. Agregue una cláusula final" else "que inicialice la matriz de cmd al estilo NT incluso si la otra' else if (osName.equals ("Windows NT") 'devuelve falso. – depthfirstdesigner

+0

¿La solución con SteamGobbler es solo para un servidor de Windows? Si estoy usando Unix, ¿no sucedería? – Dejell

13

Use ProcessBuilder. Después de llamar a start() obtendrá un objeto Process desde donde se puede obtener los flujos de stderr y stdout.

ACTUALIZACIÓN: ProcessBuilder le da más control; No tiene que usarlo pero a la larga me resulta más fácil. Especialmente la capacidad de redirigir stderr a stdout, lo que significa que solo tiene que aspirar una secuencia.

+3

¿Y cómo puedo obtener mi salida de un 'OutputStream'? – pihentagy

2

Runtime.exec() devuelve un objeto de proceso, desde el que puede extraer el resultado de cualquier comando que haya ejecutado.

1

El uso de Runtime.exec le proporciona un proceso. Puede utilizar estos getInputStream para obtener la salida estándar de este proceso, y poner este flujo de entrada en una cadena, a través de un StringBuffer por ejemplo.

5

Use Plexus Utils, es utilizado por Maven para ejecutar todos los procesos externos.

Commandline commandLine = new Commandline(); 
commandLine.setExecutable(executable.getAbsolutePath()); 

Collection<String> args = getArguments(); 

for (String arg : args) { 
    Arg _arg = commandLine.createArg(); 
    _arg.setValue(arg); 
} 

WriterStreamConsumer systemOut = new WriterStreamConsumer(console); 
WriterStreamConsumer systemErr = new WriterStreamConsumer(console); 

returnCode = CommandLineUtils.executeCommandLine(commandLine, systemOut, systemErr, 10); 
if (returnCode != 0) { 
    // bad 
} else { 
    // good 
} 
+1

¿Por qué usar una biblioteca externa? cuando el lenguaje central tiene una alternativa perfectamente adecuada? – PaulJWilliams

+1

Existen algunas diferencias que pueden no verse al principio. 1. si llama a Process.waitFor() lo hará bloque, esto significa que DEBE leer la salida del proceso; de lo contrario, el proceso esperará hasta que esté disponible el búfer de salida (salida de la consola). si eliges esta ruta (obteniendo el resultado por ti mismo) no debes usar waitFor(). 2. si sondeas, entonces tienes que agregarte código para manejarlo mientras esperas leer el resultado. El propósito de bibliotecas como Plexus Utils - 246k- es ayudarte a evitar reinventar la rueda una y otra vez :) –

+0

Ant hace lo mismo, puedes usarlo si quieres, hay una tarea central que se puede llamar (con un contexto apropiado de hormigas inicializadas) para realizar esta tarea, pero prefiero Plexus Utils ya que es más pequeño (incluso puedes quitar todo, excepto el paquete cli, lo que significa que tendrás menos de 50k), dedicado y resistente a la estabilidad (ya que está incluido en Maven 2) –

2

VerboseProcess clase de utilidad de jcabi-log puede ayudarle a:

String output = new VerboseProcess(
    new ProcessBuilder("executable with output") 
).stdout(); 

La única dependencia que necesita:

<dependency> 
    <groupId>com.jcabi</groupId> 
    <artifactId>jcabi-log</artifactId> 
    <version>0.7.5</version> 
</dependency> 
2

Ésta es mi clase de ayuda estado utilizando durante años. Una pequeña clase. Tiene la clase streamgobbler de JavaWorld para arreglar fugas de recursos de JVM. No sé si aún es válido para JVM6 y JVM7, pero no duele. Helper puede leer el buffer de salida para un uso posterior.

import java.io.*; 

/** 
* Execute external process and optionally read output buffer. 
*/ 
public class ShellExec { 
    private int exitCode; 
    private boolean readOutput, readError; 
    private StreamGobbler errorGobbler, outputGobbler; 

    public ShellExec() { 
     this(false, false); 
    } 

    public ShellExec(boolean readOutput, boolean readError) { 
     this.readOutput = readOutput; 
     this.readError = readError; 
    } 

    /** 
    * Execute a command. 
    * @param command command ("c:/some/folder/script.bat" or "some/folder/script.sh") 
    * @param workdir working directory or NULL to use command folder 
    * @param wait wait for process to end 
    * @param args 0..n command line arguments 
    * @return process exit code 
    */ 
    public int execute(String command, String workdir, boolean wait, String...args) throws IOException { 
     String[] cmdArr; 
     if (args != null && args.length > 0) { 
      cmdArr = new String[1+args.length]; 
      cmdArr[0] = command; 
      System.arraycopy(args, 0, cmdArr, 1, args.length); 
     } else { 
      cmdArr = new String[] { command }; 
     } 

     ProcessBuilder pb = new ProcessBuilder(cmdArr); 
     File workingDir = (workdir==null ? new File(command).getParentFile() : new File(workdir)); 
     pb.directory(workingDir); 

     Process process = pb.start(); 

     // Consume streams, older jvm's had a memory leak if streams were not read, 
     // some other jvm+OS combinations may block unless streams are consumed. 
     errorGobbler = new StreamGobbler(process.getErrorStream(), readError); 
     outputGobbler = new StreamGobbler(process.getInputStream(), readOutput); 
     errorGobbler.start(); 
     outputGobbler.start(); 

     exitCode = 0; 
     if (wait) { 
      try { 
       process.waitFor(); 
       exitCode = process.exitValue();     
      } catch (InterruptedException ex) { } 
     } 
     return exitCode; 
    } 

    public int getExitCode() { 
     return exitCode; 
    } 

    public boolean isOutputCompleted() { 
     return (outputGobbler != null ? outputGobbler.isCompleted() : false); 
    } 

    public boolean isErrorCompleted() { 
     return (errorGobbler != null ? errorGobbler.isCompleted() : false); 
    } 

    public String getOutput() { 
     return (outputGobbler != null ? outputGobbler.getOutput() : null);   
    } 

    public String getError() { 
     return (errorGobbler != null ? errorGobbler.getOutput() : null);   
    } 

//******************************************** 
//********************************************  

    /** 
    * StreamGobbler reads inputstream to "gobble" it. 
    * This is used by Executor class when running 
    * a commandline applications. Gobblers must read/purge 
    * INSTR and ERRSTR process streams. 
    * http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4 
    */ 
    private class StreamGobbler extends Thread { 
     private InputStream is; 
     private StringBuilder output; 
     private volatile boolean completed; // mark volatile to guarantee a thread safety 

     public StreamGobbler(InputStream is, boolean readStream) { 
      this.is = is; 
      this.output = (readStream ? new StringBuilder(256) : null); 
     } 

     public void run() { 
      completed = false; 
      try { 
       String NL = System.getProperty("line.separator", "\r\n"); 

       InputStreamReader isr = new InputStreamReader(is); 
       BufferedReader br = new BufferedReader(isr); 
       String line; 
       while ((line = br.readLine()) != null) { 
        if (output != null) 
         output.append(line + NL); 
       } 
      } catch (IOException ex) { 
       // ex.printStackTrace(); 
      } 
      completed = true; 
     } 

     /** 
     * Get inputstream buffer or null if stream 
     * was not consumed. 
     * @return 
     */ 
     public String getOutput() { 
      return (output != null ? output.toString() : null); 
     } 

     /** 
     * Is input stream completed. 
     * @return 
     */ 
     public boolean isCompleted() { 
      return completed; 
     } 

    } 

} 

Aquí hay un ejemplo de salida de lectura del script .vbs pero trabajos similares para scripts de linux sh.

ShellExec exec = new ShellExec(true, false); 
    exec.execute("cscript.exe", null, true, 
     "//Nologo", 
     "//B",   // batch mode, no prompts 
     "//T:320",  // timeout seconds 
     "c:/my/script/test1.vbs", // unix path delim works for script.exe 
     "script arg 1", 
     "script arg 2", 
    ); 
    System.out.println(exec.getOutput()); 
4

Para procesos que no generan mucha salida, creo que esta sencilla solución que utiliza Apache IOUtils es suficiente:

Process p = Runtime.getRuntime().exec("script"); 
p.waitFor(); 
String output = IOUtils.toString(p.getInputStream()); 
String errorOutput = IOUtils.toString(p.getErrorStream()); 

Advertencia: Sin embargo, si el proceso genera una gran cantidad de la producción, este enfoque puede causar problemas, como se menciona en el Process class JavaDoc:

El subproceso creado no tiene su propio terminal o consola. Todas sus operaciones estándar io (es decir, stdin, stdout, stderr) se redirigirán al proceso principal a través de tres flujos (getOutputStream(), getInputStream(), getErrorStream()). El proceso principal utiliza estas secuencias para alimentar la entrada y obtener resultados del subproceso. Debido a que algunas plataformas nativas solo proporcionan un tamaño de búfer limitado para flujos de entrada y salida estándar, si no se escribe rápidamente el flujo de entrada o se lee el flujo de salida del subproceso, es posible que el subproceso se bloquee e incluso se bloquee.

Cuestiones relacionadas