1. Ein- und Ausgabestreams eines Prozesses
Wenn Sie aus Java einen externen Prozess starten, öffnet sich zwischen ihm und Ihrem Programm ein echter Mini-Dialog – drei Kommunikationskanäle.
Der erste Kanal – die Standardausgabe. Alles, was der externe Prozess „auf den Bildschirm“ schreiben möchte, können Sie über Process.getInputStream() abgreifen. Der zweite Kanal – der Fehlerstrom. Wenn der Prozess meckert oder eine Fehlermeldung ausgibt, erfahren Sie das über Process.getErrorStream(). Und schließlich der dritte – die Standardeingabe. Darüber können Sie dem Prozess Daten schicken, wenn er etwas von Ihnen erwartet. Das geschieht mit Process.getOutputStream().
Etwas verwirrend ist, dass die Namen der Streams scheinbar vertauscht sind. Beim Prozess ist InputStream das, was Sie lesen (seine Ausgabe), und OutputStream das, wohin Sie schreiben (seine Eingabe). Aus Sicht Ihres Java-Programms ist es aber logisch: Sie lesen, was der Prozess liefert, und schreiben, was Sie ihm übergeben wollen.
Schema der Prozess-Streams
+-------------------+ +---------------------+
| Ihr Programm | <-------> | Externer Prozess |
+-------------------+ +---------------------+
| |
| getOutputStream() --------> stdin
| getInputStream() <-------- stdout
| getErrorStream() <-------- stderr
2. Die Prozessausgabe lesen (stdout)
Lernen wir, das zu lesen, was der externe Prozess auf den Bildschirm schreibt. Das kann das Ergebnis eines Befehls sein, die Programmversion, eine Erfolgsmeldung – was auch immer.
Beispiel 1: Ausgabe des Befehls java -version lesen
Starten wir zuerst den Prozess und lesen dann seine Ausgabe.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class ProcessOutputExample {
public static void main(String[] args) throws IOException {
// Prozess starten: der Befehl hängt von Ihrem Betriebssystem ab!
ProcessBuilder pb = new ProcessBuilder("java", "-version");
Process process = pb.start();
// Fehlerstrom lesen (java -version schreibt gewöhnlich dorthin!)
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Process output: " + line);
}
}
}
}
Achtung! Der Befehl java -version schreibt sein Ergebnis nicht in das normale stdout, sondern in den Fehlerstrom stderr. Das ist kein Bug, sondern eine Besonderheit von Java. Deshalb lesen wir process.getErrorStream(). Für die meisten anderen Befehle (zum Beispiel echo, ls, dir) verwenden Sie getInputStream().
Beispiel 2: Ausgabe des Befehls echo lesen
ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
Process process = pb.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Process output: " + line);
}
}
Warum braucht man einen BufferedReader?
Der Standard-Eingabestream ist ein Bytestrom (InputStream). Um ihn als Zeilen zu lesen, verwenden wir die Kombination aus InputStreamReader (wandelt Bytes in Zeichen um) + BufferedReader (praktisches zeilenweises Lesen).
3. In den Eingabestrom des Prozesses schreiben (stdin)
Manchmal erwartet der externe Prozess Daten von uns. Zum Beispiel wenn Sie ein Skript starten, das um die Eingabe eines Benutzernamens bittet, oder einen Rechner, der Zahlen erwartet.
Dafür verwenden Sie process.getOutputStream() – ja, genau: Sie „schreiben“ in den Stream des Prozesses.
Beispiel 3: Daten an einen Prozess übergeben
Nehmen wir an, Sie haben ein einfaches Python-Skript, das eine Zeile liest und sie zurück ausgibt:
echo_input.py:
# echo_input.py
line = input()
print("You said:", line)
Starten wir es aus Java und senden eine Zeile:
import java.io.*;
public class ProcessStdinExample {
public static void main(String[] args) throws IOException {
ProcessBuilder pb = new ProcessBuilder("python", "echo_input.py");
Process process = pb.start();
// In stdin des Prozesses schreiben
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(process.getOutputStream()))) {
writer.write("Hallo aus Java!\n");
writer.flush();
}
// stdout des Prozesses lesen
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Antwort des Prozesses: " + line);
}
}
}
}
Vergessen Sie flush() nicht – ansonsten können Daten im Puffer „stecken bleiben“ und den Prozess nicht erreichen.
4. Fehlerbehandlung: stderr lesen
Ein externer Prozess kann uns nicht nur mit Ergebnissen erfreuen, sondern auch „über das Leben klagen“ – Fehler schreiben. Wenn Sie den Fehlerstrom nicht lesen, können Sie wichtige Informationen verpassen oder sogar in eine Blockade geraten (Deadlock), wenn der Fehlerpuffer überläuft.
Beispiel 4: stderr eines Prozesses lesen
ProcessBuilder pb = new ProcessBuilder("java", "-version");
Process process = pb.start();
// Fehler lesen
try (BufferedReader errorReader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = errorReader.readLine()) != null) {
System.out.println("Fehler des Prozesses: " + line);
}
}
Warum ist es wichtig, beide Streams zu lesen?
Wenn ein Prozess sehr viel in stdout oder stderr schreibt und Sie diese Streams nicht lesen, kann der Puffer des Betriebssystems überlaufen und der Prozess blockiert – er wartet, bis jemand Platz schafft. Daher ist es gute Praxis, beide Streams parallel zu lesen (oder wenigstens abwechselnd).
5. Praxis: Befehl starten, Ausgabe lesen, Fehler verarbeiten
Fassen wir alles oben Beschriebene zu einem kleinen Utility zusammen, das einen externen Befehl startet, dessen Standardausgabe und Fehler liest und alles auf dem Bildschirm ausgibt.
Beispiel 5: Universeller Befehlsstart
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class RunCommand {
public static void main(String[] args) throws Exception {
if (args.length == 0) {
System.out.println("Geben Sie einen Befehl zum Starten an, z. B.: java RunCommand ls -l");
return;
}
var process = new ProcessBuilder(args).start();
// Getrennte Threads zum Lesen von stdout und stderr starten
var t1 = Thread.ofPlatform().start(() -> readStream(process.inputReader(), "[stdout]"));
var t2 = Thread.ofPlatform().start(() -> readStream(process.errorReader(), "[stderr]"));
int exitCode = process.waitFor(); // auf das Ende des Prozesses warten
t1.join();
t2.join();
System.out.println("Prozess wurde mit Code beendet: " + exitCode);
}
private static void readStream(BufferedReader reader, String prefix) {
try (reader) {
reader.lines().forEach(line -> System.out.println(prefix + " " + line));
} catch (Exception e) {
System.err.println("Fehler beim Lesen von " + prefix + ": " + e.getMessage());
}
}
}
Dieser Ansatz hilft, einen Deadlock zu vermeiden und keine Fehlermeldung zu verlieren!
6. Kurz zu Zeichencodierungen
Standardmäßig verwendet InputStreamReader die Systemkodierung. Wenn der externe Prozess in einer anderen Kodierung schreibt (z. B. UTF-8, während Sie Windows-1251 verwenden), kann die Ausgabe als „Kauderwelsch“ erscheinen. Geben Sie die Kodierung explizit an:
new InputStreamReader(process.getInputStream(), "UTF-8")
7. Beispiel: Python-Skript starten und Daten übergeben
Angenommen, Sie haben ein Python-Skript, das mehrere Zeilen an der Eingabe erwartet und anschließend ein Ergebnis ausgibt.
adder.py:
# adder.py
a = int(input())
b = int(input())
print(a + b)
Aus Java können Sie ihm Zahlen senden und die Antwort lesen:
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
public class PythonAdder {
public static void main(String[] args) throws Exception {
var process = new ProcessBuilder("python", "adder.py").start();
// Zwei Zahlen an stdin des Python-Skripts senden
try (var writer = process.outputWriter(StandardCharsets.UTF_8)) {
writer.println("10");
writer.println("25");
}
// Ergebnis aus stdout mit inputReader lesen
try (BufferedReader reader = process.inputReader(StandardCharsets.UTF_8)) {
reader.lines().forEach(line -> System.out.println("Antwort von Python: " + line));
}
int exitCode = process.waitFor();
System.out.println("Python wurde mit Code beendet: " + exitCode);
}
}
8. Warum Deadlock? Wie man ihn vermeidet
Ein Deadlock ist eine Situation, in der sich Prozess und Programm gegenseitig endlos warten lassen. Zum Beispiel: Wenn der Ausgabepuffer des Prozesses überläuft und Sie diesen Stream nicht lesen, stoppt der Prozess. Wenn Sie auf das Ende des Prozesses warten, er aber darauf wartet, dass Sie ihm etwas schreiben – ebenfalls ein Deadlock.
So vermeiden Sie ihn:
- Lesen Sie immer beide Streams (stdout und stderr).
- Für lange/geschwätzige Prozesse eigene Threads zum Lesen verwenden.
- Schreiben und Lesen nicht gleichzeitig im selben Thread blockieren.
9. Typische Fehler beim Umgang mit Prozess-Ein-/Ausgabestreams
Fehler Nr. 1: Nur einen Stream lesen (stdout oder stderr).
Wenn Sie nur stdout lesen, der Prozess aber eine Fehlermeldung in stderr schreibt, kann der Fehlerpuffer überlaufen – der Prozess hängt. Lösung: beide Streams lesen (am besten in separaten Threads).
Fehler Nr. 2: Falsche Zeichenkodierung.
Wenn Sie die nötige Kodierung nicht angeben, kann die Ausgabe unlesbar sein. Prüfen Sie immer, in welcher Kodierung der externe Prozess schreibt (z. B. explizit "UTF-8" angeben oder StandardCharsets.UTF_8 verwenden).
Fehler Nr. 3: Streams nicht geschlossen.
Offene Streams können zu Ressourcenlecks führen. Verwenden Sie try-with-resources oder schließen Sie alle Streams explizit.
Fehler Nr. 4: flush() beim Schreiben in stdin des Prozesses nicht aufgerufen.
Wenn flush() vergessen wird, kommen Daten möglicherweise nicht beim Prozess an – er wartet dann ewig auf Eingabe.
Fehler Nr. 5: Plattformunterschiede.
Befehle und Syntax können zwischen Windows und Linux/Mac variieren. Prüfen Sie das Betriebssystem über System.getProperty("os.name") und wählen Sie Befehle entsprechend.
Fehler Nr. 6: Ausnahmen nicht behandelt.
Die Arbeit mit Prozessen geht oft mit IOException einher. Behandeln Sie Ausnahmen immer, damit das Programm nicht abstürzt.
GO TO FULL VERSION