2009-05-19 3 views
50

Quando si verificano problemi di rete sulle macchine client, mi piacerebbe essere in grado di eseguire alcune righe di comando e inviare per e-mail i risultati di esse a me stesso.Acquisizione di stdout durante la chiamata a Runtime.exec

Ho trovato che Runtime.exec mi consentirà di eseguire comandi arbitrari, ma la raccolta dei risultati in una stringa è più interessante.

Mi rendo conto che potrei reindirizzare l'output a un file e quindi leggere dal file, ma il mio senso sprezzante mi sta dicendo che esiste un modo più elegante di farlo.

Suggerimenti?

+0

Dai un'occhiata a questo [articolo] (http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html). – kgiannakakis

risposta

46

È necessario acquisire sia l'errore std out che std err durante il processo. È quindi possibile scrivere std su un file/mail o simile.

Vedere this article per ulteriori informazioni, e in particolare notare il meccanismo StreamGobbler che cattura stdout/err in thread separati. Questo è essenziale per prevenire il blocco ed è la fonte di numerosi errori se non lo fai correttamente!

+0

Ho notato che se il comando dà un sacco di output il flusso del codice continuerà prima che i Gobblers siano in uscita. Devi chiamare .join() su di loro prima di tornare. – Zitrax

+0

_Extremely_ metodo efficace e semplice. Una cosa da notare però è che l'inizializzazione dell'array 'cmd' nel metodo Main sembra essere un po 'datata per Windows 7. Aggiungere una clausola" else "finale che inizializza l'array cmd allo stile NT anche se l'altro' else if (osName.equals ("Windows NT") 'torna falso – depthfirstdesigner

+0

La soluzione con SteamGobbler è solo per un server Windows? Se sto usando Unix- non succederebbe? – Dejell

13

Utilizzare ProcessBuilder. Dopo aver chiamato start() riceverai un oggetto Process da cui puoi ottenere i flussi stderr e stdout.

AGGIORNAMENTO: ProcessBuilder offre un maggiore controllo; Non devi usarlo, ma a lungo andare lo trovo più facile. Soprattutto la possibilità di reindirizzare lo stderr allo stdout, il che significa che devi solo succhiare un flusso.

+3

E come posso ottenere il mio output da un 'OutputStream'? – pihentagy

2

Runtime.exec() restituisce un oggetto Processo, da cui è possibile estrarre l'output di qualsiasi comando eseguito.

1

Utilizzo di Runtime.exec fornisce un processo. È possibile utilizzare lo standard getInputStream per ottenere lo stdout di questo processo e inserire questo flusso di input in una stringa, ad esempio tramite StringBuffer.

5

Utilizzare Plexus Utils, viene utilizzato da Maven per eseguire tutti i processi esterni.

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

Perché usare una libreria esterna quando il linguaggio principale ha un'alternativa perfettamente idonea – PaulJWilliams

+1

Ci sono alcune differenze che potrebbero non essere viste all'inizio. 1. se si chiama Process.waitFor() blocco, questo significa che DEVE leggere l'output del processo altrimenti il ​​processo attenderà fino a quando il buffer di output (output della console) sarà disponibile. se si sceglie questo percorso (ottenendo l'output da soli) non è necessario utilizzare waitFor(). 2. Se si esegue il polling, è necessario aggiungere il proprio codice per gestirlo mentre si è in attesa di leggere l'output. Lo scopo di librerie come Plexus Utils - 246k- è quello di aiutarti a evitare di reinventare la ruota più volte :) –

+0

Ant fa la stessa cosa, puoi usarla se vuoi, c'è un compito principale che può essere chiamato (con un corretto contesto inizializzato di formiche) per eseguire questa operazione, ma preferisco Plexus Utils poiché è più piccolo (puoi anche rimuovere tutto tranne il pacchetto cli, che significa che avrai meno di 50k), dedicato e prova di essere stabile (poiché è incluso in Maven 2) –

2

VerboseProcess classe di utilità da jcabi-log può aiutare:

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

L'unica dipendenza è necessario:

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

Questa è la mia classe di supporto usato per anni. Una piccola classe. Ha una classe streamgobbler JavaWorld per correggere le perdite di risorse JVM. Non so se ancora valido per JVM6 e JVM7 ma non fa male. Helper può leggere il buffer di output per un uso successivo.

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

    } 

} 

Ecco un esempio di lettura di uscita da .vbs sceneggiatura, ma opere analoghe per gli script sh Linux.

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

Per i processi che non generano molto in uscita, penso che questa semplice soluzione che utilizza Apache IOUtils è sufficiente:

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

Caveat: Tuttavia, se il processo genera un sacco di potenza, questo approccio può causare problemi, come indicato nello Process class JavaDoc:

Il sottoprocesso creato non ha il proprio terminale o console. Tutte le sue operazioni standard io (cioè stdin, stdout, stderr) verranno reindirizzate al processo padre attraverso tre flussi (getOutputStream(), getInputStream(), getErrorStream()). Il processo padre utilizza questi flussi per alimentare l'input e ottenere l'output dal sottoprocesso. Poiché alcune piattaforme native forniscono solo dimensioni del buffer limitate per flussi di input e output standard, l'errore di scrivere prontamente il flusso di input o leggere il flusso di output del sottoprocesso può causare il blocco del sottoprocesso e persino il deadlock.