Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 17 - Utility-Klassen I |
Die Klasse Runtime des Pakets java.lang ermöglicht die Interaktion eines Java-Programms mit dem Bestandteil seiner Laufzeitumgebung, der dafür verantwortlich ist, Programme zu starten und zu beenden. Dabei ist es insbesondere möglich, mit seiner Hilfe externe Programme und Kommandos zu starten, mit ihnen zu kommunizieren und ihren Zustand zu überwachen.
Objekte der Klasse Runtime dürfen vom Programm nicht selbst instanziert werden, sondern werden über die statische Methode getRuntime der Klasse Runtime beschafft:
public static Runtime getRuntime() |
java.lang.Runtime |
Um ein externes Programm zu starten, gibt es vier Methoden mit dem Namen exec:
public Process exec(String command) throws IOException public Process exec(String command, String[] envp) throws IOException public Process exec(String[] cmdarray) throws IOException public Process exec(String[] cmdarray, String[] envp) throws IOException |
java.lang.Runtime |
Das auszuführende Kommando kann wahlweise als Einzelstring angegeben werden (Kommandoname plus Argumente, getrennt durch Leerzeichen) oder die einzelnen Bestandteile können in einem Array übergeben werden. Das optionale zweite Argument envp ermöglicht es, dem zu startenden Programm eine Liste von Umgebungsvariablen zu übergeben.
Das folgende Listing zeigt die einfachste Form der Anwendung von exec. Das Programm startet exemplarisch den notepad-Editor unter Windows:
001 /* Listing1708.java */ 002 003 public class Listing1708 004 { 005 public static void main(String[] args) 006 { 007 try { 008 Runtime.getRuntime().exec("notepad"); 009 } catch (Exception e) { 010 System.err.println(e.toString()); 011 } 012 } 013 } |
Listing1708.java |
Leider macht die API-Dokumentation keine Angaben darüber, in welchen Verzeichnissen nach dem auszuführenden Programm gesucht wird. In den aktuellen Implementierungen des JDK scheint es jedoch so zu sein, dass alle Verzeichnisse durchsucht werden, die in der PATH-Umgebungsvariablen angegeben sind. Unter Windows werden dabei aber offensichtlich nur Dateien mit den Erweiterungen .com und .exe automatisch berücksichtigt. Soll dagegen eine .bat-Datei gestartet werden, muss die Erweiterung .bat explizit angegeben werden. |
|
exec gibt ein Objekt des Typs Process zurück, das dazu verwendet werden kann, mit dem gestarteten Programm zu interagieren. Dazu definiert Process folgende Methoden:
public abstract int waitFor() throws InterruptedException public abstract int exitValue() public abstract void destroy() |
java.lang.Process |
Ein Aufruf von waitFor terminiert erst, wenn der zugehörige Prozess beendet wurde. Diese Methode kann also dazu verwendet werden, auf das Ende des gestarteten Programms zu warten. Das ist beispielsweise sinnvoll, um den Rückgabewert des Programms auszuwerten oder um mit von ihm erzeugten oder veränderten Daten zu arbeiten. Wird waitFor nicht aufgerufen, kann auf aktuellen Betriebssystemen wohl davon ausgegangen werden, dass das externe Programm parallel zum Java-Programm gestartet wird und asynchron weiterläuft. Diese Aussage ist allerdings mit Vorsicht zu genießen, denn spezifiziert ist dieses Verhalten nicht. Im Zweifel hilft ausprobieren.
Nach Ende des externen Programms kann mit exitValue sein Rückgabewert abgefragt werden. Dieser gibt bei vielen Programmen an, ob es fehlerfrei ausgeführt werden konnte oder nicht. Das ist aber nicht zwangsläufig so. Unter Windows wird insbesondere beim Aufruf von Batch-Dateien und bei der expliziten Ausführung eines Kommandos in einen eigenen Kommandointerpreter der Rückgabewert nicht weitergegeben. In diesem Fall liefert exitValue immer den Wert 0. Wird exitValue aufgerufen, wenn der Prozess noch läuft, gibt es eine IllegalThreadStateException.
Die Methode destroy dient dazu, das externe Programm abzubrechen. Es handelt sich hierbei nicht um das normale Beenden eines Programms, sondern um einen harten Abbruch. Ungesicherte Änderungen gehen also verloren und es können Inkonsistenzen in manipulierten Daten entstehen.
Das Process-Objekt bietet zusätzlich die Möglichkeit, die Standardein- und -ausgabe des externen Kommandos umzuleiten und aus dem eigenen Programm heraus anzusprechen:
public OutputStream getOutputStream() public abstract InputStream getInputStream() public abstract InputStream getErrorStream() |
java.lang.Process |
getInputStream und getErrorStream liefern einen InputStream, mit dem die Ausgaben des Prozesses auf Standardausgabe und Standardfehler gelesen werden können (siehe auch Kapitel 20). Von getOutputStream wird ein OutputStream zur Verfügung gestellt, mit dem Daten in die Standardeingabe des Prozesses geschrieben werden können. Auf diese Weise lassen sich Programme, die über Standardein- und -ausgabe kommunizieren, fernsteuern bzw. fernabfragen.
Das folgende Programm fasst die wichtigsten Möglichkeiten zusammen:
001 /* RunCommand.java */ 002 003 import java.io.*; 004 005 public class RunCommand 006 { 007 enum Mode 008 { 009 MODE_UNCONNECTED, 010 MODE_WAITFOR, 011 MODE_CATCHOUTPUT; 012 } 013 014 private static void runCommand(String cmd, Mode mode) 015 throws IOException 016 { 017 Runtime rt = Runtime.getRuntime(); 018 System.out.println("Running " + cmd); 019 Process pr = rt.exec(cmd); 020 if (mode == Mode.MODE_WAITFOR) { 021 System.out.println("waiting for termination"); 022 try { 023 pr.waitFor(); 024 } catch (InterruptedException e) { 025 } 026 } else if (mode == Mode.MODE_CATCHOUTPUT) { 027 System.out.println("catching output"); 028 BufferedReader procout = new BufferedReader( 029 new InputStreamReader(pr.getInputStream()) 030 ); 031 String line; 032 while ((line = procout.readLine()) != null) { 033 System.out.println(" OUT> " + line); 034 } 035 } 036 try { 037 System.out.println( 038 "done, return value is " + pr.exitValue() 039 ); 040 } catch (IllegalThreadStateException e) { 041 System.out.println( 042 "ok, process is running asynchronously" 043 ); 044 } 045 } 046 047 private static void runShellCommand(String cmd, Mode mode) 048 throws IOException 049 { 050 String prefix = ""; 051 String osName = System.getProperty("os.name"); 052 osName = osName.toLowerCase(); 053 if (osName.indexOf("windows") != -1) { 054 prefix = "command.com /c "; 055 } else if (osName.indexOf("linux") != -1) { 056 prefix = "sh -c "; 057 } 058 059 if (prefix.length() <= 0) { 060 System.out.println( 061 "unknown OS: don\'t know how to invoke shell" 062 ); 063 } else { 064 runCommand(prefix + cmd, mode); 065 } 066 } 067 068 public static void main(String[] args) 069 { 070 try { 071 if (args.length <= 0) { 072 System.out.println( 073 "Usage: java RunCommand [-shell] " + 074 "[-waitfor|-catchoutput] <command>" 075 ); 076 System.exit(1); 077 } 078 boolean shell = false; 079 Mode mode = Mode.MODE_UNCONNECTED; 080 String cmd = ""; 081 for (int i = 0; i < args.length; ++i) { 082 if (args[i].startsWith("-")) { 083 if (args[i].equals("-shell")) { 084 shell = true; 085 } else if (args[i].equals("-waitfor")) { 086 mode = Mode.MODE_WAITFOR; 087 } else if (args[i].equals("-catchoutput")) { 088 mode = Mode.MODE_CATCHOUTPUT; 089 } 090 } else { 091 cmd = args[i]; 092 } 093 } 094 if (shell) { 095 runShellCommand(cmd, mode); 096 } else { 097 runCommand(cmd, mode); 098 } 099 } catch (Exception e) { 100 System.err.println(e.toString()); 101 } 102 } 103 } |
RunCommand.java |
Das Hauptprogramm erwartet das zu startende Programm und seine Parameter als Argumente. Wird die Option »-catchoutput« angegeben, liest das Programm die Ausgaben des gestarteten Programms und gibt sie auf seiner eigenen Standardausgabe aus. Wird »-waitfor« angegeben, wartet das Programm auf das Ende des gestarteten Programms, ohne dessen Ausgaben anzuzeigen. In beiden Fällen wird schließlich der Rückgabewert des Programms ausgegeben. Durch Angabe der Option »-shell« kann das externe Programm mit einem separaten Kommandointerpreter gestartet werden. Das ist beispielsweise nützlich, um Shell-Kommandos auszuführen, die nicht als eigenständige Programmdateien existieren.
Der folgende Aufruf unter Windows verwendet das Beispielprogramm,
um das interne MS-DOS-Kommando »set« auszuführen (es
gibt die Inhalte aller Umgebungsvariablen aus):
java RunCommand -shell -catchoutput set
Seine Ausgabe könnte etwa so aussehen:
Running command.com /c set
catching output
OUT> winbootdir=C:\WINDOWS
OUT> COMSPEC=C:\COMMAND.COM
OUT> TEMP=C:\tmp
OUT> TMP=c:\tmp
OUT> USER=guido
OUT> windir=C:\WINDOWS
OUT> PATH=C:\JDK1.6\BIN;C:\WINDOWS;C:\WINDOWS\COMMAND;
OUT> CLASSPATH=.;c:\arc\prog\java
OUT> PROMPT=$p--$g
done, return value is 0
Analog kann der Aufruf unter Linux die Inhalte aller Umgebungsvariablen
ausgeben:
...
OUT> XDG_DATA_DIRS='/usr/share/gnome:/usr/local/share/:/usr/share/'
OUT> XFILESEARCHPATH='/usr/dt/app-defaults/%L/Dt'
OUT> _='/usr/bin/java'
done, return value is 0
Die Übergabe des Programms an einen separaten Kommandointerpreter erfolgt in der Methode runShellCommand ab Zeile 047. Wie ein derartiger Aufruf ausgeführt wird, ist natürlich betriebssystem- und konfigurationsabhängig. Das Beispielprogramm versucht, einige brauchbare Varianten für gängige Betriebssysteme vorzudefinieren. Weitere können leicht hinzugefügt werden. |
|
Während des Tests von RunCommand gab es mitunter Schwierigkeiten beim Ausführen interner DOS-Programme unter Windows 7. Während sich beispielsweise das Kommando set problemlos aufrufen ließ, gab es beim Aufruf von dir Hänger, nach denen die MS-DOS-Task hart abgebrochen werden musste. Die JDK Bug Database listet eine ganze Reihe von Problemen in Zusammenhang mit dem Aufruf von 16-Bit-Programmen unter Windows auf. Sie rühren unter anderem daher, dass die Ein- und Ausgabepuffer der DOS-Programme so klein sind, dass die Programme mitunter schon blockieren, bevor die aufrufende Applikation die Chance hatte, eine Verbindung zu ihnen herzustellen. Echte Workarounds für diese Probleme scheinen nicht bekannt zu sein. Beim Aufruf von 32-Bit-Programmen treten die Probleme offenbar nicht auf. |
|
Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0 |
<< | < | > | >> | API | © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de |