Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 17 - Utility-Klassen I

17.4 Die Klasse RunTime



17.4.1 Grundlagen

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
Listing 17.8: Starten von notepad.exe

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.

 Hinweis 

17.4.2 Interaktion mit dem externen Programm

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
Listing 17.9: Starten externer Programme

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.

 Hinweis 

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.

 Warnung 


 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