Capture stdout lors de l’appel de Runtime.exec

Lorsque vous rencontrez des problèmes de mise en réseau sur des machines client, j’aimerais pouvoir exécuter quelques lignes de commande et m’en envoyer les résultats par courrier électronique.

J’ai trouvé que Runtime.exec me permet d’exécuter des commandes arbitraires, mais la collecte des résultats dans une chaîne est plus intéressante.

Je me rends compte que je pourrais redirect la sortie vers un fichier, puis lire le fichier, mais mon sens du terme me dit qu’il existe une façon plus élégante de le faire.

Suggestions?

Vous devez capturer à la fois la std out et std err dans le processus. Vous pouvez ensuite écrire std out dans un fichier / mail ou similaire.

Consultez cet article pour plus d’informations et notez en particulier le mécanisme StreamGobbler qui capture stdout / err dans des threads distincts. Ceci est essentiel pour éviter le blocage et est la source de nombreuses erreurs si vous ne le faites pas correctement!

Utilisez ProcessBuilder . Après avoir appelé start (), vous obtiendrez un object Process à partir duquel vous pourrez obtenir les stream stderr et stdout.

UPDATE: ProcessBuilder vous donne plus de contrôle; Vous n’avez pas besoin de l’utiliser, mais je trouve cela plus facile à long terme. Surtout la possibilité de redirect stderr vers stdout, ce qui signifie que vous ne devez aspirer qu’un seul stream.

Utilisez Plexus Utils , il est utilisé par Maven pour exécuter tous les processus externes.

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

Pour les processus qui ne génèrent pas beaucoup de résultats, je pense que cette solution simple qui utilise Apache IOUtils est suffisante:

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

Avertissement: Cependant, si votre processus génère beaucoup de résultats, cette approche peut poser problème, comme mentionné dans la classe Process JavaDoc :

Le sous-processus créé ne possède pas son propre terminal ou console. Toutes ses opérations standard io (c’est-à-dire stdin, stdout, stderr) seront redirigées vers le processus parent via trois stream (getOutputStream (), getInputStream (), getErrorStream ()). Le processus parent utilise ces stream pour alimenter les entrées et obtenir des sorties du sous-processus. Étant donné que certaines plates-formes natives ne fournissent qu’une taille de tampon limitée pour les stream d’entrée et de sortie standard, le fait de ne pas écrire rapidement le stream d’entrée ou de lire le stream de sortie du sous-processus peut entraîner le blocage du sous-processus, voire un blocage.

Runtime.exec () renvoie un object Process, à partir duquel vous pouvez extraire le résultat de la commande que vous avez exécutée.

VerboseProcess classe d’utilitaires VerboseProcess de jcabi-log peut vous aider à:

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

La seule dépendance dont vous avez besoin:

  com.jcabi jcabi-log 0.7.5  

C’est ma classe d’aide qui utilise depuis des années. Une petite classe. Il a la classe JavaWorld streamgobbler pour réparer les fuites de ressources JVM. Je ne sais pas si c’est toujours valable pour JVM6 et JVM7 mais ça ne fait pas mal. Helper peut lire le tampon de sortie pour une utilisation ultérieure.

 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(Ssortingng command, Ssortingng workdir, boolean wait, Ssortingng...args) throws IOException { Ssortingng[] cmdArr; if (args != null && args.length > 0) { cmdArr = new Ssortingng[1+args.length]; cmdArr[0] = command; System.arraycopy(args, 0, cmdArr, 1, args.length); } else { cmdArr = new Ssortingng[] { 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 Ssortingng getOutput() { return (outputGobbler != null ? outputGobbler.getOutput() : null); } public Ssortingng 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 SsortingngBuilder 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 SsortingngBuilder(256) : null); } public void run() { completed = false; try { Ssortingng NL = System.getProperty("line.separator", "\r\n"); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); Ssortingng 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 Ssortingng getOutput() { return (output != null ? output.toSsortingng() : null); } /** * Is input stream completed. * @return */ public boolean isCompleted() { return completed; } } } 

Voici un exemple de sortie de script .vbs mais des travaux similaires pour les scripts 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()); 

L’utilisation de Runtime.exec vous donne un processus. Vous pouvez utiliser getInputStream pour obtenir la sortie standard de ce processus et placer ce stream d’entrée dans une chaîne, via un SsortingngBuffer par exemple.