web-dev-qa-db-fra.com

Capture stdout lors de l'appel de Runtime.exec

Lorsque je rencontre des problèmes de mise en réseau sur les machines clientes, j'aimerais pouvoir exécuter quelques lignes de commande et m'en envoyer les résultats par e-mail.

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 pouvais rediriger la sortie vers un fichier, puis lire à partir du fichier, mais mon sens perspicace me dit qu'il existe une façon plus élégante de le faire.

Suggestions?

54
Allain Lalonde

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

Voir cet article pour plus d'informations, et notez en particulier le mécanisme StreamGobbler qui capture stdout/err dans des threads séparés. Ceci est essentiel pour éviter le blocage et est à l'origine de nombreuses erreurs si vous ne le faites pas correctement!

47
Brian Agnew

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

MISE À JOUR: ProcessBuilder vous donne plus de contrôle; Vous n'êtes pas obligé de l'utiliser, mais je trouve cela plus facile à long terme. Surtout la possibilité de rediriger stderr vers stdout, ce qui signifie que vous n'avez qu'à aspirer un flux.

13
basszero

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

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
}
7
adrian.tarau

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

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

Mise en garde: Cependant, si votre processus génère beaucoup de sortie, cette approche peut provoquer des problèmes, comme mentionné dans le classe de processus JavaDoc :

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

5
erwaman

Ceci est ma classe d'aide depuis des années. Une petite classe. Il a la classe JavaWorld streamgobbler pour corriger les fuites de ressources JVM. Je ne sais pas si elle est toujours valide pour JVM6 et JVM7 mais ne fait pas de 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(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;
        }

    }

}

Voici un exemple de lecture de sortie à partir d'un script .vbs mais des travaux similaires pour les scripts 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());
3
Whome

VerboseProcess classe utilitaire de jcabi-log peut vous aider:

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

La seule dépendance dont vous avez besoin:

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

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

2
PaulJWilliams

L'utilisation de Runtime.exec vous donne un processus. Vous pouvez les utiliser getInputStream pour obtenir la sortie standard de ce processus, et mettre ce flux d'entrée dans une chaîne, via un StringBuffer par exemple.

1
Valentin Rocher