Catturare lo stdout quando si chiama Runtime.exec

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.

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

Mi rendo conto che potrei redirect 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?

È necessario acquisire sia la std out che std err nel processo. È quindi ansible scrivere std su un file / mail o simile.

Vedi questo articolo per maggiori informazioni, e in particolare nota 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!

Usa ProcessBuilder . Dopo aver chiamato start () si ottiene un object Process da cui è ansible ottenere i flussi stderr e stdout.

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

Usa Plexus Utils , è usato da Maven per eseguire tutti i processi esterni.

 Commandline commandLine = new Commandline(); commandLine.setExecutable(executable.getAbsolutePath()); Collection 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 } 

Per i processi che non generano molto output, penso che questa semplice soluzione che utilizza Apache IOUtils sia sufficiente:

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

Avvertenza: tuttavia, se il vostro processo genera molto output, questo approccio potrebbe causare problemi, come menzionato nella class Process 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 piattaforms native forniscono solo dimensioni del buffer limitate per flussi di input e output standard, l’errore di scrivere prontamente il stream di input o leggere il stream di output del sottoprocesso può causare il blocco del sottoprocesso e persino il deadlock.

Runtime.exec () restituisce un object Process, da cui è ansible estrarre l’output di qualsiasi comando eseguito.

VerboseProcess class di utility VerboseProcess di jcabi-log può aiutarti a:

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

L’unica dipendenza di cui hai bisogno:

  com.jcabi jcabi-log 0.7.5  

Questa è la mia class di aiuto che ho usato per anni. Una piccola class. Ha una class 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 output di lettura dallo script .vbs ma simile funziona per gli script di 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()); 

Usando Runtime.exec ti dà un processo. È ansible utilizzare getInputStream per ottenere lo stdout di questo processo e inserire questo stream di input in una stringa, ad esempio tramite StringBuffer.