58 Stimmen

Capture von stdout beim Aufrufen von Runtime.exec

Bei Netzwerkproblemen auf Client-Maschinen würde ich gerne in der Lage sein, einige Befehlszeilen auszuführen und die Ergebnisse per E-Mail an mich zu senden.

Ich habe herausgefunden, dass Runtime.exec es mir ermöglichen wird, beliebige Befehle auszuführen, aber das Sammeln der Ergebnisse in einem String ist interessanter.

Ich realisiere, dass ich die Ausgabe in eine Datei umleiten und dann aus der Datei lesen könnte, aber mein Spinnensinn sagt mir, dass es eine elegantere Möglichkeit gibt, es zu tun.

Vorschläge?

0 Stimmen

Schauen Sie sich diesen Artikel an.

48voto

Brian Agnew Punkte 260470

Sie müssen sowohl den std out als auch den std err im Prozess erfassen. Sie können dann std out in eine Datei/Mail oder Ähnliches schreiben.

Sehen Sie sich diesen Artikel für weitere Informationen an, und beachten Sie insbesondere den StreamGobbler-Mechanismus, der stdout/err in separaten Threads erfasst. Dies ist unerlässlich, um Blockierungen zu verhindern und ist die Quelle zahlreicher Fehler, wenn Sie es nicht ordnungsgemäß tun!

16voto

basszero Punkte 29038

Verwenden Sie ProcessBuilder. Nach dem Aufruf von start() erhalten Sie ein Process-Objekt, aus dem Sie die stderr- und stdout-Streams erhalten können.

UPDATE: ProcessBuilder gibt Ihnen mehr Kontrolle; Sie müssen es nicht verwenden, aber ich finde es auf lange Sicht einfacher. Besonders die Möglichkeit, stderr nach stdout umzuleiten, was bedeutet, dass Sie nur einen Stream verarbeiten müssen.

7voto

adrian.tarau Punkte 3085

Verwenden Sie Plexus Utils, es wird von Maven verwendet, um alle externen Prozesse auszuführen.

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) {
    // schlecht
} else {
    // gut
}

7voto

erwaman Punkte 2991

Für Prozesse, die nicht viel Ausgabe erzeugen, denke ich, dass diese einfache Lösung, die Apache IOUtils verwendet, ausreichend ist:

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

Einschränkung: Wenn Ihr Prozess jedoch viele Ausgaben erzeugt, kann dieser Ansatz Probleme verursachen, wie im Process-Klassen JavaDoc erwähnt:

Der erstellte Unterprozess verfügt nicht über sein eigenes Terminal oder seine eigene Konsole. Alle seine Standard-Ein/Ausgänge (d.h. stdin, stdout, stderr) werden über drei Streams (getOutputStream(), getInputStream(), getErrorStream()) an den Elternprozess weitergeleitet. Der Elternprozess verwendet diese Streams, um Eingabe an den Unterprozess zu liefern und Ausgabe vom Unterprozess zu erhalten. Da einige native Plattformen nur eine begrenzte Puffergröße für Standard-Ein- und Ausgabeströme bereitstellen, kann ein versäumtes Schreiben in den Eingabestrom oder das Lesen des Ausgabestroms des Unterprozesses dazu führen, dass der Unterprozess blockiert und sogar in einen Deadlock gerät.

5voto

Whome Punkte 9949

Dies ist meine Hilfsklasse, die ich seit Jahren benutze. Eine kleine Klasse. Sie hat die JavaWorld-StreamGobbler-Klasse, um JVM-Ressourcenlecks zu beheben. Ich weiß nicht, ob sie immer noch für JVM 6 und JVM 7 gültig ist, aber es schadet nicht. Der Helfer kann den Ausgabepuffer zur späteren Verwendung lesen.

import java.io.*;

/**
 * Führt einen externen Prozess aus und liest optional den Ausgabepuffer.
 */
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;
    }

    /**
     * Führt einen Befehl aus.
     * @param command   Befehl ("c:/some/folder/script.bat" oder "some/folder/script.sh")
     * @param workdir   Arbeitsverzeichnis oder NULL, um das Befehlsverzeichnis zu verwenden
     * @param wait  Warte auf das Ende des Prozesses
     * @param args 0..n Befehlszeilenargumente
     * @return Prozess-Exitcode
     */
    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();

        // Streams verbrauchen, ältere JVMs hatten ein Speicherleck, wenn die Streams nicht gelesen wurden,
        // einige andere JVM+OS-Kombinationen können blockieren, es sei denn, die Streams werden konsumiert.
        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 liest den InputStream, um ihn zu "verschlucken".
     * Dies wird von der Executor-Klasse verwendet, wenn 
     * Befehlszeilenanwendungen ausgeführt werden. Gobblers müssen die INSTR- und ERRSTR-Prozessstreams lesen.
     * 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;
        }

        /**
         * Gibt den InputStream-Puffer zurück oder null, wenn der Stream
         * nicht verbraucht wurde.
         * @return
         */
        public String getOutput() {
            return (output != null ? output.toString() : null);
        }

        /**
         * Ist der InputStream abgeschlossen.
         * @return
         */
        public boolean isCompleted() {
            return completed;
        }

    }

}

Hier ist ein Beispiel zum Lesen der Ausgabe aus einem .vbs-Skript, das jedoch ähnlich für Linux-sh-Skripte funktioniert.

   ShellExec exec = new ShellExec(true, false);
   exec.execute("cscript.exe", null, true,
      "//Nologo",
      "//B",            // Batch-Modus, keine Eingabeaufforderungen
      "//T:320",        // Timeout in Sekunden
      "c:/my/script/test1.vbs",  // Unix-Pfad-Trennzeichen funktioniert für script.exe
      "Skript-Argument 1",
      "Skript-Argument 2",
   );
   System.out.println(exec.getOutput());

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X