CodeGym /Kursy /JAVA 25 SELF /Odczyt wyjścia procesu, praca ze stdin/stdout

Odczyt wyjścia procesu, praca ze stdin/stdout

JAVA 25 SELF
Poziom 61, Lekcja 1
Dostępny

1. Strumienie wejścia/wyjścia procesu

Gdy uruchamiasz proces zewnętrzny z Javy, między nim a twoim programem otwiera się prawdziwy mini-dialog – trzy kanały komunikacji.

Pierwszy kanał to standardowe wyjście. Wszystko, co proces zewnętrzny chce pokazać „na ekranie”, możesz przechwycić przez Process.getInputStream(). Drugi kanał to strumień błędów. Jeśli proces postanowi poskarżyć się lub wypisać komunikat o błędzie, dowiesz się o tym przez Process.getErrorStream(). I wreszcie trzeci – standardowe wejście. Tędy możesz wysłać do procesu jakieś dane, jeśli czegoś od ciebie oczekuje. Robi się to za pomocą Process.getOutputStream().

To może trochę mylić, bo nazwy strumieni wyglądają, jakby były „na odwrót”. Dla procesu InputStream to to, co czytasz (jego wyjście), a OutputStream to to, dokąd piszesz (jego wejście). Ale z perspektywy twojego programu w Javie wszystko jest logiczne: czytasz to, co proces wypisuje, i zapisujesz to, co chcesz mu przekazać.

Schemat strumieni procesu

+-------------------+           +---------------------+
|   Twój program    | <-------> |   Proces zewnętrzny |
+-------------------+           +---------------------+
        |                                |
        |  getOutputStream()  -------->  stdin
        |  getInputStream()   <--------  stdout
        |  getErrorStream()   <--------  stderr

2. Odczyt wyjścia procesu (stdout)

Nauczmy się czytać to, co proces zewnętrzny wypisuje na ekran. Może to być wynik polecenia, wersja programu, komunikat o powodzeniu – cokolwiek.

Przykład 1: Odczyt wyjścia polecenia java -version

Najpierw uruchomimy proces, a potem odczytamy jego wyjście.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class ProcessOutputExample {
    public static void main(String[] args) throws IOException {
        // Tworzymy proces: polecenie zależy od twojego systemu operacyjnego!
        ProcessBuilder pb = new ProcessBuilder("java", "-version");
        Process process = pb.start();

        // Czytamy strumień błędów (java -version zwykle tam pisze!)
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getErrorStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Process output: " + line);
            }
        }
    }
}

Uwaga! Polecenie java -version wypisuje wynik nie do zwykłego stdout, lecz do strumienia błędów stderr. To nie błąd, a cecha Javy. Dlatego czytamy process.getErrorStream(). Dla większości innych poleceń (na przykład echo, ls, dir) używaj getInputStream().

Przykład 2: Odczyt wyjścia polecenia echo

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);
    }
}

Dlaczego potrzebny jest BufferedReader?

Standardowy strumień wejścia to strumień bajtów (InputStream). Aby czytać go jako linie tekstu, używamy zestawu InputStreamReader (przekształca bajty w znaki) + BufferedReader (wygodne czytanie wierszami).

3. Zapis do wejściowego strumienia procesu (stdin)

Czasem proces zewnętrzny oczekuje od nas danych. Na przykład gdy uruchamiasz skrypt proszący o nazwę użytkownika albo kalkulator czekający na liczby.

Do tego użyj process.getOutputStream() – tak, właśnie tak: „piszesz” do strumienia procesu.

Przykład 3: Przekazywanie danych do procesu

Załóżmy, że masz prosty skrypt w Pythonie, który czyta wiersz i wypisuje go z powrotem:

echo_input.py:

# echo_input.py
line = input()
print("You said:", line)

Uruchomimy go z Javy i wyślemy mu wiersz:

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();

        // Piszemy do stdin procesu
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(process.getOutputStream()))) {
            writer.write("Pozdrowienia z Javy!\n");
            writer.flush();
        }

        // Odczytujemy stdout procesu
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Odpowiedź procesu: " + line);
            }
        }
    }
}

Nie zapominaj o flush() – w przeciwnym razie dane mogą „utknąć” w buforze i nie trafić do procesu.

4. Obsługa błędów: odczyt stderr

Proces zewnętrzny może nie tylko cieszyć nas wynikami, ale też narzekać – wypisywać błędy. Jeśli nie czytasz strumienia błędów, możesz przeoczyć ważne informacje, a nawet doprowadzić do zawieszenia (deadlock), jeśli bufor błędów się zapełni.

Przykład 4: Odczyt stderr procesu

ProcessBuilder pb = new ProcessBuilder("java", "-version");
Process process = pb.start();

// Czytamy błędy
try (BufferedReader errorReader = new BufferedReader(
        new InputStreamReader(process.getErrorStream()))) {
    String line;
    while ((line = errorReader.readLine()) != null) {
        System.out.println("Błąd procesu: " + line);
    }
}

Dlaczego warto czytać oba strumienie?

Jeśli proces bardzo dużo zapisuje do stdout lub stderr, a ty nie czytasz tych strumieni, bufor systemu operacyjnego może się zapełnić i proces „zawiśnie”, czekając aż ktoś zwolni miejsce. Dlatego dobrą praktyką jest czytać oba strumienie równolegle (albo przynajmniej naprzemiennie).

5. Praktyka: uruchamianie polecenia, odczyt wyjścia, obsługa błędów

Połączmy wszystko powyżej w jedno małe narzędzie, które uruchamia polecenie zewnętrzne, czyta jego standardowe wyjście i błędy oraz wypisuje wszystko na ekran.

Przykład 5: Uniwersalne uruchamianie polecenia

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("Podaj polecenie do uruchomienia, na przykład: java RunCommand ls -l");
            return;
        }

        var process = new ProcessBuilder(args).start();

        // Uruchamiamy oddzielne wątki do czytania stdout i stderr
        var t1 = Thread.ofPlatform().start(() -> readStream(process.inputReader(), "[stdout]"));
        var t2 = Thread.ofPlatform().start(() -> readStream(process.errorReader(), "[stderr]"));

        int exitCode = process.waitFor(); // czekamy na zakończenie procesu
        t1.join();
        t2.join();

        System.out.println("Proces zakończył się kodem: " + 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("Błąd odczytu " + prefix + ": " + e.getMessage());
        }
    }
}

Taka metoda pozwala uniknąć deadlocka i nie stracić żadnego błędu!

6. Krótko o kodowaniach

Domyślnie InputStreamReader używa kodowania systemowego. Jeśli proces zewnętrzny zapisuje w innym kodowaniu (na przykład UTF-8, a u ciebie Windows-1251), wyjście może wyglądać jak „krzaczki”. Wskaż kodowanie jawnie:

new InputStreamReader(process.getInputStream(), "UTF-8")

7. Przykład: uruchomienie skryptu Pythona i przekazanie mu danych

Załóżmy, że masz skrypt w Pythonie, który oczekuje kilku wierszy na wejściu, a następnie wypisuje wynik.

adder.py:

# adder.py
a = int(input())
b = int(input())
print(a + b)

Z Javy możesz wysłać mu liczby i odczytać odpowiedź:

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();

        // Wysyłamy dwie liczby do stdin skryptu w Pythonie
        try (var writer = process.outputWriter(StandardCharsets.UTF_8)) {
            writer.println("10");
            writer.println("25");
        }

        // Czytamy wynik ze stdout za pomocą inputReader
        try (BufferedReader reader = process.inputReader(StandardCharsets.UTF_8)) {
            reader.lines().forEach(line -> System.out.println("Odpowiedź od Pythona: " + line));
        }

        int exitCode = process.waitFor();
        System.out.println("Python zakończył się kodem: " + exitCode);
    }
}

8. Skąd się bierze deadlock? Jak go uniknąć

Deadlock to sytuacja, gdy proces i twój program czekają na siebie nawzajem w nieskończoność. Na przykład jeśli bufor wyjścia procesu się zapełni, a ty nie czytasz tego strumienia, proces się zatrzyma. Jeśli czekasz na zakończenie procesu, a on czeka, aż mu coś zapiszesz – to również deadlock.

Jak uniknąć:

  • Zawsze czytaj oba strumienie (stdout i stderr).
  • Dla długotrwałych/wielomównych procesów używaj osobnych wątków do odczytu.
  • Nie blokuj zapisu i odczytu jednocześnie w jednym wątku.

9. Typowe błędy przy pracy ze strumieniami wejścia/wyjścia procesu

Błąd nr 1: Odczyt tylko jednego strumienia (stdout lub stderr).
Jeśli czytasz tylko stdout, a proces zapisuje błąd do stderr, bufor błędów może się zapełnić – proces się zawiesi. Rozwiązanie: czytać oba strumienie (najlepiej w osobnych wątkach).

Błąd nr 2: Nieprawidłowe kodowanie.
Jeśli nie wskażesz odpowiedniego kodowania, wyjście może być nieczytelne. Zawsze sprawdzaj, w jakim kodowaniu zapisuje proces zewnętrzny (na przykład jawnie ustaw "UTF-8" lub użyj StandardCharsets.UTF_8).

Błąd nr 3: Niezamknięte strumienie.
Otwarte strumienie mogą prowadzić do wycieków zasobów. Używaj try-with-resources lub zamykaj wszystkie strumienie jawnie.

Błąd nr 4: Brak flush() podczas zapisu do stdin procesu.
Jeśli zapomnisz wywołać flush(), dane mogą nie trafić do procesu – będzie on wiecznie czekał na wejście.

Błąd nr 5: Różnice między platformami.
Polecenia i składnia mogą się różnić między Windows i Linux/Mac. Sprawdzaj system przez System.getProperty("os.name") i dobieraj polecenia odpowiednio.

Błąd nr 6: Brak obsługi wyjątków.
Praca z procesami często wiąże się z IOException. Zawsze obsługuj wyjątki, aby program nie zakończył się awaryjnie.

1
Zadanie
JAVA 25 SELF, poziom 61, lekcja 1
Niedostępne
Tajne przesłanie 📩
Tajne przesłanie 📩
1
Zadanie
JAVA 25 SELF, poziom 61, lekcja 1
Niedostępne
Dyspozytor Stacji Kosmicznej 🚀
Dyspozytor Stacji Kosmicznej 🚀
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION