55 votes

Capture de stdout lors de l'appel à Runtime.exec

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

J'ai découvert 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 réalise que je pourrais rediriger la sortie vers un fichier, puis lire à partir de ce fichier, mais mon intuition me dit qu'il y a une façon plus élégante de le faire.

Des suggestions ?

0 votes

Jetez un coup d'œil à ceci article .

47voto

Brian Agnew Points 143181

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

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

0 votes

J'ai remarqué que si la commande donne beaucoup de résultats, le flux de code continuera avant que les Gobblers aient fini de produire. Il faut appeler .join() sur eux avant de revenir.

0 votes

Extrêmement méthode efficace et simple. Une chose à noter cependant est que le cmd L'initialisation du tableau cmd dans la méthode Main semble un peu dépassée pour Windows 7. Ajoutez une clause finale "else" qui initialise le tableau cmd au style NT même si les autres éléments de la méthode Main ne sont pas encore initialisés. else if( osName.equals( "Windows NT" ) revient faux.

0 votes

Est-ce que la solution avec SteamGobbler est seulement pour un serveur Windows ? Si j'utilise Unix, cela n'arrivera pas ?

14voto

basszero Points 14539

Utilisez ProcessBuilder . Après avoir appelé start() vous obtiendrez un Processus à partir duquel vous pouvez 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 le trouve plus facile à long terme. En particulier, la possibilité de rediriger stderr vers stdout, ce qui signifie que vous n'avez à aspirer qu'un seul flux.

3 votes

Et comment puis-je obtenir ma sortie d'un OutputStream ?

7voto

erwaman Points 35

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 sorties, cette approche peut poser des problèmes, comme le mentionne l'article intitulé Classe de processus JavaDoc :

Le sous-processus créé ne dispose pas de son propre terminal ou de sa propre console. Toutes ses opérations io standard (c'est-à-dire stdin, stdout, stderr) seront redirigées vers le processus parent à travers trois flux (getOutputStream(), getInputStream(), getErrorStream()). Le processus parent utilise ces flux pour alimenter le sous-processus en entrée et en sortie. É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, le fait de ne pas écrire rapidement le flux d'entrée ou de ne pas lire rapidement le flux de sortie du sous-processus peut entraîner le blocage du sous-processus, voire même son blocage.

7voto

adrian.tarau Points 2023

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
}

1 votes

Pourquoi utiliser une bibliothèque externe lorsque le langage de base offre une alternative parfaitement adaptée ?

2 votes

Il y a quelques différences qui peuvent ne pas être vues au début. 1. si vous appelez Process.waitFor(), il se bloquera, ce qui signifie que vous DEVEZ lire la sortie du processus, sinon le processus attendra que le tampon de sortie (sortie console) soit disponible. si vous choisissez cette voie (obtenir la sortie vous-même), vous ne devez pas utiliser waitFor(). 2. si vous polluez, alors vous devez ajouter vous-même du code pour gérer cela pendant que vous attendez de lire la sortie. Le but des bibliothèques comme Plexus Utils - 246k- est de vous éviter de réinventer la roue encore et encore :)

0 votes

Ant fait la même chose, vous pouvez l'utiliser si vous voulez, il y a une tâche de base qui peut être appelée (avec un contexte ant correctement initialisé) pour effectuer cette tâche, mais je préfère Plexus Utils car il est plus petit (vous pouvez même enlever tout sauf le paquet cli, ce qui signifie que vous aurez moins de 50k), dédié et la preuve d'être stable (car il est inclus dans Maven 2).

4voto

Whome Points 2303

C'est mon cours d'aide que j'utilise depuis des années. Une petite classe. Elle contient la classe streamgobbler de JavaWorld pour réparer les fuites de ressources de la JVM. Je ne sais pas si c'est encore valable pour JVM6 et JVM7 mais ça 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 la sortie de .vbs script mais des travaux similaires pour les scripts sh de 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());

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X