CodeGym /Kurslar /JAVA 25 SELF /Prosesin çıxışını oxuma, stdin/stdout ilə iş

Prosesin çıxışını oxuma, stdin/stdout ilə iş

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

1. Prosesin giriş/çıxış axınları

Java-dan xarici prosesi işə saldığınızda, onunla proqramınız arasında həqiqi bir mini-dialoq açılır — üç rabitə kanalı.

Birinci kanal — standart çıxışdır. Xarici prosesin «ekrana» göstərmək istədiyi hər şeyi Process.getInputStream() vasitəsilə tuta bilərsiniz. İkinci kanal — xəta axınıdır. Proses şikayətlənmək və ya xəta mesajı vermək istəsə, bunu Process.getErrorStream() vasitəsilə biləcəksiniz. Nəhayət, üçüncü — standart girişdir. Əgər proses sizdən nəsə gözləyirsə, bu kanal vasitəsilə ona məlumat göndərə bilərsiniz. Bu, Process.getOutputStream() ilə edilir.

Adların bir az çaşdırıcı olması mümkündür. Proses üçün InputStream — sizin oxuduğunuz şeydir (onun çıxışı), a OutputStream — sizin yazdığınız yer (onun girişi). Amma Java proqramınızın gözü ilə baxanda hər şey məntiqlidir: siz prosesin verdiyini oxuyursunuz və ona ötürmək istədiyinizi yazırsınız.

Proses axınlarının sxemi

+-------------------+           +---------------------+
|  Sizin proqramınız | <-------> |    Xarici proses    |
+-------------------+           +---------------------+
        |                                |
        |  getOutputStream()  -------->  stdin
        |  getInputStream()   <--------  stdout
        |  getErrorStream()   <--------  stderr

2. Prosesin çıxışını oxuma (stdout)

Gəlin, xarici prosesin ekrana çıxardığı məlumatı oxumağı öyrənək. Bu, əmrin nəticəsi, proqramın versiyası, uğur mesajı — qısası, istənilən şey ola bilər.

Nümunə 1: java -version əmrinin çıxışını oxuma

Əvvəlcə prosesi işə salaq, sonra isə onun çıxışını oxuyaq.

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

public class ProcessOutputExample {
    public static void main(String[] args) throws IOException {
        // Proses yaradırıq: əmr sizin OS-dən asılıdır!
        ProcessBuilder pb = new ProcessBuilder("java", "-version");
        Process process = pb.start();

        // Xəta axınını oxuyuruq (java -version adətən ora yazır!)
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getErrorStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Process output: " + line);
            }
        }
    }
}

Diqqət! java -version əmri nəticəsini adi stdout-a yox, xəta axını stderr-ə yazır. Bu, xəta deyil, Java-nın xüsusiyyətidir. Buna görə də biz process.getErrorStream() oxuyuruq. Digər əksər əmrlər üçün (məsələn, echo, ls, dir) — getInputStream() istifadə edin.

Nümunə 2: echo əmrinin çıxışını oxuma

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

Niyə BufferedReader lazımdır?

Standart giriş axını — bayt axınıdır (InputStream). Onu sətir kimi oxumaq üçün InputStreamReader (baytları simvollara çevirir) + BufferedReader (sətir-sətir rahat oxumaq üçün) birləşməsindən istifadə edirik.

3. Prosesin giriş axınına yazmaq (stdin)

Bəzən xarici proses bizdən məlumat gözləyir. Məsələn, istifadəçi adını daxil etməyinizi istəyən skript və ya ədəd gözləyən kalkulyator başladırsınızsa.

Bunun üçün process.getOutputStream() istifadə edin — bəli, məhz belə: siz prosesin axınına «yazırsınız».

Nümunə 3: Məlumatı prosesə ötürmək

Tutaq ki, sizdə bir sətir oxuyub onu geri çap edən sadə Python skripti var:

echo_input.py:

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

Onu Java-dan işə salaq və bir sətir göndərək:

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

        // Prosesin stdin-ə yazırıq
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(process.getOutputStream()))) {
            writer.write("Java-dan salam!\n");
            writer.flush();
        }

        // Prosesin stdout-unu oxuyuruq
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Prosesin cavabı: " + line);
            }
        }
    }
}

flush()-ı unutmayın — əks halda məlumatlar buferdə «ilişib» prosesə çatmaya bilər.

4. Xətaların işlənməsi: stderr-in oxunması

Xarici proses bizi nəticələrlə sevindirməklə yanaşı, şikayət də edə bilər — xətalar yaza bilər. Xəta axınını oxumasanız, vacib məlumatı qaçıra və hətta bufer dolarsa ilişmə (deadlock) ilə qarşılaşa bilərsiniz.

Nümunə 4: Prosesin stderr-ni oxuma

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

// Xətaları oxuyuruq
try (BufferedReader errorReader = new BufferedReader(
        new InputStreamReader(process.getErrorStream()))) {
    String line;
    while ((line = errorReader.readLine()) != null) {
        System.out.println("Proses xətası: " + line);
    }
}

Niyə hər iki axını oxumaq vacibdir?

Əgər proses stdout və ya stderr-ə çoxlu məlumat yazırsa və siz bu axınları oxumursunuzsa, OS buferi dolub, proses kimsə yeri boşaldana qədər «ilişib» qala bilər. Buna görə yaxşı təcrübə — hər iki axını paralel oxumaqdır (heç olmasa növbə ilə).

5. Praktika: əmrin işə salınması, çıxışın oxunması, xətaların işlənməsi

Gəlin yuxarıda deyilənləri bir kiçik utilitdə birləşdirək: o, xarici əmri işə salır, onun standart çıxışını və xətalarını oxuyur və hamısını ekrana çıxarır.

Nümunə 5: Əmrin universal işə salınması

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("İcra etmək üçün əmri göstərin, məsələn: java RunCommand ls -l");
            return;
        }

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

        // stdout və stderr üçün ayrı oxuma saplarını (threads) işə salırıq
        var t1 = Thread.ofPlatform().start(() -> readStream(process.inputReader(), "[stdout]"));
        var t2 = Thread.ofPlatform().start(() -> readStream(process.errorReader(), "[stderr]"));

        int exitCode = process.waitFor(); // prosesin başa çatmasını gözləyirik
        t1.join();
        t2.join();

        System.out.println("Proses bu kodla başa çatdı: " + 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("Oxuma xətası " + prefix + ": " + e.getMessage());
        }
    }
}

Belə yanaşma deadlock-dan qaçmağa və heç bir xətanı itirməməyə imkan verir!

6. Kodlaşdırmalar haqqında qısa

Standart olaraq InputStreamReader sistem kodlaşdırmasından istifadə edir. Xarici proses başqa kodlaşdırmada yazırsa (məsələn, o, UTF-8-dən istifadə edir, sizdə isə Windows-1251), çıxış «qarışıq simvollarla» görünə bilər. Kodlaşdırmanı açıq şəkildə göstərin:

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

7. Nümunə: Python skriptinin işə salınması və ona məlumatların ötürülməsi

Tutaq ki, bir neçə sətir giriş gözləyən və sonra nəticəni çap edən Python skriptiniz var.

adder.py:

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

Java-dan ona ədədlər göndərə və cavabı oxuya bilərsiniz:

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

        // Python skriptinin stdin-ə iki ədəd göndəririk
        try (var writer = process.outputWriter(StandardCharsets.UTF_8)) {
            writer.println("10");
            writer.println("25");
        }

        // Nəticəni inputReader vasitəsilə stdout-dan oxuyuruq
        try (BufferedReader reader = process.inputReader(StandardCharsets.UTF_8)) {
            reader.lines().forEach(line -> System.out.println("Python-dan cavab: " + line));
        }

        int exitCode = process.waitFor();
        System.out.println("Python bu kodla başa çatdı: " + exitCode);
    }
}

8. Niyə deadlock baş verir? Onun qarşısını necə almaq olar

Deadlock — proseslə proqramınızın bir-birini sonsuza qədər gözləməsi vəziyyətidir. Məsələn, prosesin çıxış buferi dolub, siz həmin axını oxumursunuzsa, proses dayanacaq. Siz prosesin başa çatmasını gözləyirsiniz, o isə sizdən nəsə yazmağınızı — bu da deadlock-dır.

Necə qarşısını almaq olar:

  • Həmişə hər iki axını oxuyun (stdoutstderr).
  • Uzun və ya çox yazan proseslər üçün oxumağı ayrı icra saplarında (thread-lərdə) həyata keçirin.
  • Eyni thread-də yazma və oxumanı eyni vaxtda bloklamayın.

9. Prosesin giriş/çıxış axınları ilə işdə tipik səhvlər

Səhv №1: Yalnız bir axını oxumaq (stdout və ya stderr).
Əgər yalnız stdout-u oxuyursunuzsa, proses isə xətaları stderr-ə yazırsa, xəta buferi dolub proses ilişə bilər. Həll: hər iki axını oxuyun (yaxşısı ayrı thread-lərdə).

Səhv №2: Yanlış kodlaşdırma.
Lazımi kodlaşdırmanı göstərməsəniz, çıxış oxunmaz ola bilər. Xarici prosesin hansı kodlaşdırmadan istifadə etdiyini həmişə yoxlayın (məsələn, açıq şəkildə "UTF-8" göstərin və ya StandardCharsets.UTF_8-dan istifadə edin).

Səhv №3: Axınları bağlamamaq.
Açıq qalan axınlar resurs sızmalarına gətirib çıxara bilər. try-with-resources istifadə edin və ya bütün axınları açıq şəkildə bağlayın.

Səhv №4: Prosesin stdin-ə yazarkən flush() çağırmamaq.
Əgər flush() çağırmağı unutsanız, məlumatlar prosesə çatmaya bilər — o, giriş gözləməyə davam edəcək.

Səhv №5: Platforma fərqləri.
Əmrlər və sintaksis Windows ilə Linux/Mac arasında fərqlənə bilər. System.getProperty("os.name") vasitəsilə OS-i yoxlayın və uyğun əmrlər seçin.

Səhv №6: İstisnaları emal etməmək.
Proseslərlə iş çox vaxt IOException ilə müşayiət olunur. Proqramın qəfil dayanmasının qarşısını almaq üçün istisnaları həmişə emal edin.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION