Java Runtime.getRuntime (): Abrufen der Ausgabe beim Ausführen eines Befehlszeilenprogramms

155

Ich verwende die Laufzeit, um Eingabeaufforderungsbefehle von meinem Java-Programm auszuführen. Mir ist jedoch nicht bekannt, wie ich die vom Befehl zurückgegebene Ausgabe erhalten kann.

Hier ist mein Code:

Runtime rt = Runtime.getRuntime();

String[] commands = {"system.exe", "-send" , argument};

Process proc = rt.exec(commands);

Ich habe es versucht, System.out.println(proc);aber das brachte nichts zurück. Die Ausführung dieses Befehls sollte zwei durch ein Semikolon getrennte Zahlen zurückgeben. Wie könnte ich dies in einer Variablen zum Ausdrucken bringen?

Hier ist der Code, den ich jetzt verwende:

String[] commands = {"system.exe", "-get t"};

Process proc = rt.exec(commands);

InputStream stdIn = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdIn);
BufferedReader br = new BufferedReader(isr);

String line = null;
System.out.println("<OUTPUT>");

while ((line = br.readLine()) != null)
     System.out.println(line);

System.out.println("</OUTPUT>");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);

Aber ich bekomme nichts als meine Ausgabe, aber wenn ich diesen Befehl selbst ausführe, funktioniert es gut.

user541597
quelle

Antworten:

244

Hier ist der richtige Weg:

Runtime rt = Runtime.getRuntime();
String[] commands = {"system.exe", "-get t"};
Process proc = rt.exec(commands);

BufferedReader stdInput = new BufferedReader(new 
     InputStreamReader(proc.getInputStream()));

BufferedReader stdError = new BufferedReader(new 
     InputStreamReader(proc.getErrorStream()));

// Read the output from the command
System.out.println("Here is the standard output of the command:\n");
String s = null;
while ((s = stdInput.readLine()) != null) {
    System.out.println(s);
}

// Read any errors from the attempted command
System.out.println("Here is the standard error of the command (if any):\n");
while ((s = stdError.readLine()) != null) {
    System.out.println(s);
}

Lesen Sie den Javadoc für weitere Details hier . ProcessBuilderwäre eine gute Wahl zu verwenden.

Senthil
quelle
4
@AlbertChen pwd && lsführt nicht nur eine einzelne Datei aus, wenn Sie dies in einer Shell tun, werden sowohl die /bin/pwdals auch die /bin/lsausführbaren Dateien ausgeführt. Wenn Sie solche Dinge in Java tun möchten, müssen Sie so etwas tun {"/bin/bash","-c", "pwd && ls"}. Sie haben die Frage wahrscheinlich nicht mehr, aber andere Leute könnten es tun, also dachte ich, ich könnte sie beantworten.
735Tesla
3
Ich denke, das Lesen der beiden Streams muss gleichzeitig erfolgen, denn wenn, wie in Ihrem Fall, die Ausgabe von stdStream den Puffer füllt, können Sie den Fehler-Stream nicht lesen.
Li3ro
3
Li3ro hat teilweise recht. Das Programm, das Sie gerade hören, hat einen begrenzten Puffer für stdoutund eine begrenzte stderrAusgabe. Wenn Sie sie nicht gleichzeitig hören, füllt sich einer von ihnen, während Sie den anderen lesen. Das Programm, das Sie gerade hören, blockiert dann den Versuch, in den gefüllten Puffer zu schreiben, während Ihr Programm am anderen Ende den Versuch blockiert, aus einem Puffer zu lesen, der niemals zurückkehren wird EOF. Sie müssen gleichzeitig aus beiden Streams lesen.
Gili
1
@Gili Warum ist Li3ro dann "teilweise" richtig? Ist Li3ro nicht einfach ganz und gar richtig? In diesem Fall verstehe ich nicht, warum hier seit 2011 eine falsche Antwort herumhängt und warum es über 200 positive Stimmen gibt ... Das ist verwirrend.
Andrey Tyukin
2
@ AndyTyukin Du hast recht. Alle aktuellen Antworten sind anfällig für Deadlocks. Ich ermutige sie, sie herabzustimmen, damit andere Antworten sichtbar werden. Ich habe eine neue Antwort für Ihre Bewertung veröffentlicht: stackoverflow.com/a/57949752/14731 . Hoffentlich habe ich das richtig verstanden ...
Gili
68

Ein schnellerer Weg ist folgender:

public static String execCmd(String cmd) throws java.io.IOException {
    java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
}

Welches ist im Grunde eine komprimierte Version davon:

public static String execCmd(String cmd) throws java.io.IOException {
    Process proc = Runtime.getRuntime().exec(cmd);
    java.io.InputStream is = proc.getInputStream();
    java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
    String val = "";
    if (s.hasNext()) {
        val = s.next();
    }
    else {
        val = "";
    }
    return val;
}

Ich weiß, dass diese Frage alt ist, aber ich poste diese Antwort, weil ich denke, dass dies schneller gehen kann.

735Tesla
quelle
4
Danke für die nette Antwort. Warum ist "\\ A" das Trennzeichen?
Gottfried
1
Ich erinnere mich nicht ganz an meine Logik, als ich das ursprünglich schrieb. Ich benutze diese Lösung schon eine Weile, aber ich denke, das lag daran, dass \Ain einem regulären Ausdruck der Beginn eines Strings bedeutet und ich dem Schrägstrich entkommen musste.
735Tesla
5
"\ A" ist das Glockenzeichen. "^" ist der Anfang einer Zeichenfolge in Regex und "$" ist das Ende einer Zeichenfolge in Regex. Dies ist ein Charakter, den Sie nicht sehen würden. Das Standardtrennzeichen ist laut Java-Dokumentation ein Leerzeichen. Wenn Sie dies tun, wird wahrscheinlich das vollständige Ergebnis des Befehls ausgegeben.
Hank Schultz
11

ProcessBuilderLesen und implementieren Sie neben der empfohlenen Verwendung von Senthil alle Empfehlungen von When Runtime.exec () .

Andrew Thompson
quelle
Dieses Snippet scheint nicht den Standardfehlerstrom zu verbrauchen (wie im verlinkten Artikel empfohlen). Es wird auch kein ProcessBuilderwie jetzt empfohlen zweimal verwendet. Mit a ProcessBuilderist es möglich, die Ausgabe- und Fehlerströme zusammenzuführen, um es einfacher zu machen, beide gleichzeitig zu verwenden.
Andrew Thompson
11

Wenn für die Verwendung bereits Apache commons-io im Klassenpfad verfügbar ist, können Sie Folgendes verwenden:

Process p = new ProcessBuilder("cat", "/etc/something").start();
String stderr = IOUtils.toString(p.getErrorStream(), Charset.defaultCharset());
String stdout = IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
Wirbel
quelle
7

Wir können auch Streams verwenden, um die Befehlsausgabe zu erhalten:

public static void main(String[] args) throws IOException {

        Runtime runtime = Runtime.getRuntime();
        String[] commands  = {"free", "-h"};
        Process process = runtime.exec(commands);

        BufferedReader lineReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        lineReader.lines().forEach(System.out::println);

        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        errorReader.lines().forEach(System.out::println);
    }
Jackkobec
quelle
7

Antwort von @Senthil und @Arend ( https://stackoverflow.com/a/5711150/2268559 ) erwähnt ProcessBuilder. Hier ist das Beispiel ProcessBuildermit der Angabe von Umgebungsvariablen und Arbeitsordner für den Befehl:

    ProcessBuilder pb = new ProcessBuilder("ls", "-a", "-l");

    Map<String, String> env = pb.environment();
    // If you want clean environment, call env.clear() first
    //env.clear();
    env.put("VAR1", "myValue");
    env.remove("OTHERVAR");
    env.put("VAR2", env.get("VAR1") + "suffix");

    File workingFolder = new File("/home/user");
    pb.directory(workingFolder);

    Process proc = pb.start();

    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));

    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

    // Read the output from the command:
    System.out.println("Here is the standard output of the command:\n");
    String s = null;
    while ((s = stdInput.readLine()) != null)
        System.out.println(s);

    // Read any errors from the attempted command:
    System.out.println("Here is the standard error of the command (if any):\n");
    while ((s = stdError.readLine()) != null)
        System.out.println(s);
nidalpres
quelle
6

Zum Zeitpunkt dieses Schreibens können alle anderen Antworten, die Code enthalten, zu Deadlocks führen.

Prozesse haben einen begrenzten Puffer für stdoutund eine begrenzte stderrAusgabe. Wenn Sie sie nicht gleichzeitig anhören, füllt sich einer von ihnen, während Sie versuchen, den anderen zu lesen. Beispielsweise könnten Sie darauf warten, gelesen zu werden, stdoutwährend der Prozess darauf wartet, darauf zu schreiben stderr. Sie können nicht aus dem stdoutPuffer lesen, da dieser leer ist und der Prozess nicht in den stderrPuffer schreiben kann, weil er voll ist. Sie warten alle für immer aufeinander.

Hier ist eine Möglichkeit, die Ausgabe eines Prozesses ohne das Risiko von Deadlocks zu lesen:

public final class Processes
{
    private static final String NEWLINE = System.getProperty("line.separator");

    /**
     * @param command the command to run
     * @return the output of the command
     * @throws IOException if an I/O error occurs
     */
    public static String run(String... command) throws IOException
    {
        ProcessBuilder pb = new ProcessBuilder(command).redirectErrorStream(true);
        Process process = pb.start();
        StringBuilder result = new StringBuilder(80);
        try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())))
        {
            while (true)
            {
                String line = in.readLine();
                if (line == null)
                    break;
                result.append(line).append(NEWLINE);
            }
        }
        return result.toString();
    }

    /**
     * Prevent construction.
     */
    private Processes()
    {
    }
}

Der Schlüssel ist zu verwenden, ProcessBuilder.redirectErrorStream(true)der stderrin den stdoutStream umleitet . Auf diese Weise können Sie einen einzelnen Stream lesen, ohne zwischen stdoutund wechseln zu müssen stderr. Wenn Sie dies manuell implementieren möchten, müssen Sie die Streams in zwei verschiedenen Threads verwenden , um sicherzustellen, dass Sie niemals blockieren.

Gili
quelle
Oh wow! Ich hatte nicht erwartet, dass Sie so schnell auf den Kommentar antworten würden, geschweige denn diese ewig alte Frage beantworten würden! :) Ich denke jetzt darüber nach, ein Kopfgeld zu starten. Werfen Sie später einen Blick auf Ihre Antwort. Vielen Dank!
Andrey Tyukin
1

Wenn Sie auf Kotlin schreiben, können Sie Folgendes verwenden:

val firstProcess = ProcessBuilder("echo","hello world").start()
val firstError = firstProcess.errorStream.readBytes().decodeToString()
val firstResult = firstProcess.inputStream.readBytes().decodeToString()
Android-Entwickler
quelle
0

Angepasst an die vorherige Antwort:

public static String execCmdSync(String cmd, CmdExecResult callback) throws java.io.IOException, InterruptedException {
    RLog.i(TAG, "Running command:", cmd);

    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec(cmd);

    //String[] commands = {"system.exe", "-get t"};

    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

    StringBuffer stdOut = new StringBuffer();
    StringBuffer errOut = new StringBuffer();

    // Read the output from the command:
    System.out.println("Here is the standard output of the command:\n");
    String s = null;
    while ((s = stdInput.readLine()) != null) {
        System.out.println(s);
        stdOut.append(s);
    }

    // Read any errors from the attempted command:
    System.out.println("Here is the standard error of the command (if any):\n");
    while ((s = stdError.readLine()) != null) {
        System.out.println(s);
        errOut.append(s);
    }

    if (callback == null) {
        return stdInput.toString();
    }

    int exitVal = proc.waitFor();
    callback.onComplete(exitVal == 0, exitVal, errOut.toString(), stdOut.toString(), cmd);

    return stdInput.toString();
}

public interface CmdExecResult{
    void onComplete(boolean success, int exitVal, String error, String output, String originalCmd);
}
Derek Zhu
quelle
0

Ziemlich genau wie andere Schnipsel auf dieser Seite, aber nur Dinge über eine Funktion organisieren , los geht's ...

String str=shell_exec("ls -l");

Die Klassenfunktion:

public String shell_exec(String cmd)
       {
       String o=null;
       try
         {
         Process p=Runtime.getRuntime().exec(cmd);
         BufferedReader b=new BufferedReader(new InputStreamReader(p.getInputStream()));
         String r;
         while((r=b.readLine())!=null)o+=r;
         }catch(Exception e){o="error";}
       return o;
       }
PYK
quelle
-1

Versuchen Sie, InputStreamdie Laufzeit zu lesen :

Runtime rt = Runtime.getRuntime();
String[] commands = {"system.exe", "-send", argument};
Process proc = rt.exec(commands);
BufferedReader br = new BufferedReader(
    new InputStreamReader(proc.getInputStream()));
String line;
while ((line = br.readLine()) != null)
    System.out.println(line);

Möglicherweise müssen Sie auch den Fehlerstrom ( proc.getErrorStream()) lesen, wenn der Prozess eine Fehlerausgabe druckt. Sie können den Fehlerstrom bei Verwendung auf den Eingabestream umleiten ProcessBuilder.

Brahmananda Kar
quelle