Suche…


Bemerkungen

Beachten Sie, dass die API empfiehlt, dass ab Version 1.5 die bevorzugte Methode zum Erstellen eines Prozesses die Verwendung von ProcessBuilder.start() .

Eine weitere wichtige Anmerkung ist, dass der von waitFor erzeugte Exit-Wert von dem ausgeführten Programm / Skript abhängig ist. Beispielsweise unterscheiden sich die von calc.exe erzeugten Exitcodes von notepad.exe .

Einfaches Beispiel (Java-Version <1.5)

In diesem Beispiel wird der Windows-Rechner aufgerufen. Es ist wichtig zu wissen, dass der Exit-Code entsprechend dem aufgerufenen Programm / Skript variiert.

package process.example;

import java.io.IOException;

public class App {

    public static void main(String[] args) {
        try {
            // Executes windows calculator
            Process p = Runtime.getRuntime().exec("calc.exe");

            // Wait for process until it terminates
            int exitCode = p.waitFor();

            System.out.println(exitCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Verwendung der ProcessBuilder-Klasse

Die ProcessBuilder-Klasse vereinfacht das Senden eines Befehls über die Befehlszeile. Alles, was es braucht, ist eine Liste von Strings, aus denen sich die Befehle zusammensetzen. Sie rufen einfach die start () - Methode in Ihrer ProcessBuilder-Instanz auf, um den Befehl auszuführen.

Wenn Sie ein Programm namens Add.exe haben, das zwei Argumente verwendet und diese hinzufügt, würde der Code etwa so aussehen:

List<String> cmds = new ArrayList<>();
cmds.add("Add.exe"); //the name of the application to be run
cmds.add("1"); //the first argument
cmds.add("5"); //the second argument

ProcessBuilder pb = new ProcessBuilder(cmds);

//Set the working directory of the ProcessBuilder so it can find the .exe
//Alternatively you can just pass in the absolute file path of the .exe
File myWorkingDirectory = new File(yourFilePathNameGoesHere);
pb.workingDirectory(myWorkingDirectory);

try {
    Process p = pb.start(); 
} catch (IOException e) {
    e.printStackTrace();
}

Einige Dinge zu beachten:

  • Das Array von Befehlen muss ein String-Array sein
  • Die Befehle müssen in der Reihenfolge (im Array) sein, in der sie sich befinden, wenn Sie das Programm in der Befehlszeile selbst aufrufen (dh der Name der EXE-Datei kann nicht nach dem ersten Argument stehen
  • Beim Einstellen des Arbeitsverzeichnisses müssen Sie ein File-Objekt übergeben und nicht nur den Dateinamen als String

Blockieren vs. Nicht-Blockieren von Anrufen

Wenn Sie die Befehlszeile aufrufen, sendet das Programm im Allgemeinen den Befehl und fährt dann mit der Ausführung fort.

Sie können jedoch warten, bis das aufgerufene Programm beendet ist, bevor Sie mit der Ausführung fortfahren (z. B. schreibt das aufgerufene Programm Daten in eine Datei, und Ihr Programm benötigt diese, um auf diese Daten zuzugreifen.)

Dies kann leicht durch Aufrufen der waitFor() Methode aus der zurückgegebenen Process Instanz erreicht werden.

Anwendungsbeispiel:

//code setting up the commands omitted for brevity...

ProcessBuilder pb = new ProcessBuilder(cmds);

try {
    Process p = pb.start();
    p.waitFor();
} catch (IOException e) {
    e.printStackTrack();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//more lines of code here...

ch.vorburger.exec

Das direkte Starten externer Prozesse von Java mit der rohen java.lang.ProcessBuilder-API kann etwas umständlich sein. Die Apache Commons Exec-Bibliothek macht es etwas einfacher. Die ch.vorburger.exec-Bibliothek erweitert Commons Exec noch weiter, um es wirklich bequem zu machen:

 ManagedProcess proc = new ManagedProcessBuilder("path-to-your-executable-binary")
     .addArgument("arg1")
     .addArgument("arg2")
     .setWorkingDirectory(new File("/tmp"))
     .setDestroyOnShutdown(true)
     .setConsoleBufferMaxLines(7000)
     .build();

proc.start();
int status = proc.waitForExit();
int status = proc.waitForExitMaxMsOrDestroy(3000);
String output = proc.getConsole();

proc.startAndWaitForConsoleMessageMaxMs("started!", 7000);
// use service offered by external process...
proc.destroy();

Pitfall: Runtime.exec, Process und ProcessBuilder verstehen die Shell-Syntax nicht

Mit den Runtime.exec(String ...) und Runtime.exec(String) können Sie einen Befehl als externen Prozess ausführen. 1 In der ersten Version geben Sie den Befehlsnamen und die Befehlsargumente als separate Elemente des String-Arrays an. Die Java-Laufzeitumgebung fordert das Betriebssystemlaufzeitsystem auf, den externen Befehl zu starten. Die zweite Version ist täuschend einfach zu bedienen, weist jedoch einige Fallstricke auf.

Zunächst einmal ein Beispiel für die sichere Verwendung von exec(String) :

Process p = Runtime.exec("mkdir /tmp/testDir");
p.waitFor();
if (p.exitValue() == 0) {
    System.out.println("created the directory");
}

Leerzeichen in Pfadnamen

Angenommen, wir verallgemeinern das obige Beispiel, um ein beliebiges Verzeichnis erstellen zu können:

Process p = Runtime.exec("mkdir " + dirPath);
// ...

Dies funktioniert normalerweise, dirPath jedoch fehl, wenn dirPath beispielsweise "/ home / user / My Documents" ist. Das Problem ist, dass exec(String) die Zeichenfolge in einen Befehl und Argumente aufteilt, indem einfach nach Leerzeichen gesucht wird. Die Befehlszeichenfolge:

"mkdir /home/user/My Documents"

wird aufgeteilt in:

"mkdir", "/home/user/My", "Documents"

Dies führt dazu, dass der Befehl "mkdir" fehlschlägt, da er ein Argument erwartet, nicht zwei.

Angesichts dessen versuchen einige Programmierer, den Pfadnamen in Anführungszeichen zu setzen. Das funktioniert auch nicht:

"mkdir \"/home/user/My Documents\""

wird aufgeteilt in:

"mkdir", "\"/home/user/My", "Documents\""

Die zusätzlichen doppelten Anführungszeichen, die hinzugefügt wurden, um die Leerzeichen zu "zitieren", werden wie alle anderen Zeichen ohne Leerzeichen behandelt. In der Tat wird alles, was wir zitieren oder aus den Räumen entkommen, versagen.

Um mit diesen Problemen exec(String ...) zu werden, verwenden Sie die exec(String ...) Überladung.

Process p = Runtime.exec("mkdir", dirPath);
// ...

Dies funktioniert, wenn dirpath Leerzeichen enthält, da diese Überladung von exec nicht versucht, die Argumente aufzuteilen. Die Zeichenfolgen werden unverändert an den Betriebssystem- exec Systemaufruf übergeben.

Umleitung, Pipelines und andere Shell-Syntax

Angenommen, wir möchten die Eingabe oder Ausgabe eines externen Befehls umleiten oder eine Pipeline ausführen. Zum Beispiel:

Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");

oder

Process p = Runtime.exec("find source -name *.java | xargs grep package");

(Das erste Beispiel listet die Namen aller Java-Dateien im Dateisystem auf und das zweite druckt die package 2 in den Java-Dateien in der "Quell" -Struktur.)

Diese werden nicht wie erwartet funktionieren. Im ersten Fall wird der Befehl "find" mit "2> / dev / null" als Befehlsargument ausgeführt. Es wird nicht als Weiterleitung interpretiert. Im zweiten Beispiel werden das Pipe-Zeichen ("|") und die darauf folgenden Werke dem Befehl "find" übergeben.

Das Problem hierbei ist, dass die exec Methoden und ProcessBuilder keine ProcessBuilder verstehen. Dazu gehören Umleitungen, Pipelines, variable Erweiterungen, Globbing usw.

In einigen Fällen (z. B. einfache Weiterleitung) können Sie mit ProcessBuilder den gewünschten Effekt leicht erreichen. Dies trifft jedoch im Allgemeinen nicht zu. Ein alternativer Ansatz besteht darin, die Befehlszeile in einer Shell auszuführen. zum Beispiel:

Process p = Runtime.exec("bash", "-c", 
                         "find / -name *.java -print 2>/dev/null");

oder

Process p = Runtime.exec("bash", "-c", 
                         "find source -name \\*.java | xargs grep package");

Beachten Sie jedoch, dass wir im zweiten Beispiel dem Platzhalterzeichen ("*") entgehen mussten, da der Platzhalter von "find" und nicht von der Shell interpretiert werden soll.

Integrierte Shell-Befehle funktionieren nicht

Angenommen, die folgenden Beispiele funktionieren auf einem System mit einer UNIX-ähnlichen Shell nicht:

Process p = Runtime.exec("cd", "/tmp");     // Change java app's home directory

oder

Process p = Runtime.exec("export", "NAME=value");  // Export NAME to the java app's environment

Es gibt einige Gründe, warum dies nicht funktioniert:

  1. Bei den Befehlen "cd" und "export" handelt es sich um in die Shell eingebaute Befehle. Sie existieren nicht als ausführbare Dateien.

  2. Damit die eingebauten Shell-Funktionen genau das tun können, was sie tun sollen (z. B. das Arbeitsverzeichnis ändern, die Umgebung aktualisieren), müssen sie den Ort ändern, an dem sich dieser Status befindet. Bei einer normalen Anwendung (einschließlich einer Java-Anwendung) ist der Status dem Anwendungsprozess zugeordnet. So konnte beispielsweise der untergeordnete Prozess, der den Befehl "cd" ausführen würde, das Arbeitsverzeichnis seines übergeordneten Prozesses "java" nicht ändern. Ebenso kann ein exec -Prozess nicht das Arbeitsverzeichnis für einen exec Prozess ändern.

Diese Argumentation gilt für alle integrierten Shell-Befehle.


1 - Sie können ProcessBuilder auch verwenden, dies ist jedoch für den Punkt dieses Beispiels nicht relevant.

2 - Dies ist etwas grob und bereit ... aber auch hier sind die Fehler dieses Ansatzes für das Beispiel nicht relevant.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow