CodeGym /Cours /JAVA 25 SELF /Lecture de la sortie d’un processus, gestion de stdin/std...

Lecture de la sortie d’un processus, gestion de stdin/stdout

JAVA 25 SELF
Niveau 61, Leçon 1
Disponible

1. Flux d’entrée/sortie d’un processus

Quand vous lancez un processus externe depuis Java, un véritable mini-dialogue s’ouvre entre lui et votre programme — trois canaux de communication.

Le premier canal, c’est la sortie standard. Tout ce que le processus externe veut afficher « à l’écran », vous pouvez le récupérer via Process.getInputStream(). Le deuxième canal, c’est le flux d’erreurs. Si le processus décide de se plaindre ou d’émettre un message d’erreur, vous le saurez via Process.getErrorStream(). Enfin, le troisième — l’entrée standard. Par elle, vous pouvez envoyer des données au processus s’il attend quelque chose de vous. Cela se fait avec Process.getOutputStream().

Les noms des flux peuvent troubler — on dirait qu’ils sont inversés. Pour le processus, InputStream est ce que vous lisez (sa sortie), et OutputStream est là où vous écrivez (son entrée). Mais si l’on regarde avec les yeux de votre programme Java, tout est logique : vous lisez ce que le processus émet et vous écrivez ce que vous voulez lui transmettre.

Schéma des flux du processus

+-------------------+           +---------------------+
|  Votre programme  | <-------> |  Processus externe  |
+-------------------+           +---------------------+
        |                                |
        |  getOutputStream()  -------->  stdin
        |  getInputStream()   <--------  stdout
        |  getErrorStream()   <--------  stderr

2. Lecture de la sortie du processus (stdout)

Apprenons à lire ce que le processus externe affiche à l’écran. Cela peut être le résultat d’une commande, la version d’un programme, un message de succès — n’importe quoi.

Exemple 1: Lecture de la sortie de la commande java -version

Commençons par lancer le processus, puis lisons sa sortie.

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

public class ProcessOutputExample {
    public static void main(String[] args) throws IOException {
        // Nous créons un processus : la commande dépend de votre OS !
        ProcessBuilder pb = new ProcessBuilder("java", "-version");
        Process process = pb.start();

        // Nous lisons le flux d’erreurs (java -version écrit généralement là !)
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getErrorStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Process output: " + line);
            }
        }
    }
}

Attention! La commande java -version écrit son résultat non pas dans le stdout habituel, mais dans le flux d’erreurs stderr. Ce n’est pas un bug, mais une particularité de Java. C’est pourquoi nous lisons process.getErrorStream(). Pour la plupart des autres commandes (par exemple echo, ls, dir) — utilisez getInputStream().

Exemple 2: Lecture de la sortie de la commande 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);
    }
}

Pourquoi utiliser BufferedReader?

Le flux d’entrée standard est un flux d’octets (InputStream). Pour le lire sous forme de lignes, on utilise le duo InputStreamReader (convertit les octets en caractères) + BufferedReader (pratique pour une lecture ligne par ligne).

3. Écriture dans l’entrée du processus (stdin)

Parfois, le processus externe attend des données de notre part. Par exemple, si vous lancez un script qui demande de saisir un nom d’utilisateur, ou une calculatrice qui attend des nombres.

Pour cela, utilisez process.getOutputStream() — oui, c’est bien cela : vous « écrivez » dans le flux du processus.

Exemple 3: Envoi de données au processus

Supposons que vous ayez un script Python simple qui lit une ligne et la réimprime :

echo_input.py :

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

Lançons-le depuis Java et envoyons-lui une ligne :

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

        // Nous écrivons sur le stdin du processus
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(process.getOutputStream()))) {
            writer.write("Bonjour depuis Java!\n");
            writer.flush();
        }

        // Nous lisons le stdout du processus
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Réponse du processus: " + line);
            }
        }
    }
}

N’oubliez pas flush() — sinon les données peuvent rester « coincées » dans le tampon et ne pas atteindre le processus.

4. Gestion des erreurs: lecture de stderr

Un processus externe peut non seulement nous réjouir avec des résultats, mais aussi se plaindre — écrire des erreurs. Si vous ne lisez pas le flux d’erreurs, vous pouvez manquer des informations importantes, voire obtenir un blocage (deadlock) si le tampon d’erreurs se remplit.

Exemple 4: Lecture de stderr du processus

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

// Nous lisons les erreurs
try (BufferedReader errorReader = new BufferedReader(
        new InputStreamReader(process.getErrorStream()))) {
    String line;
    while ((line = errorReader.readLine()) != null) {
        System.out.println("Erreur du processus: " + line);
    }
}

Pourquoi est-il important de lire les deux flux?

Si le processus écrit beaucoup dans stdout ou stderr et que vous ne lisez pas ces flux, le tampon de l’OS peut se remplir et le processus « gèlera », en attendant que quelqu’un libère de la place. C’est pourquoi une bonne pratique consiste à lire les deux flux en parallèle (ou au moins en alternance).

5. Pratique: lancer une commande, lire la sortie, gérer les erreurs

Regroupons tout ce qui précède dans un petit utilitaire qui lance une commande externe, lit sa sortie standard et ses erreurs, et affiche tout à l’écran.

Exemple 5: Lancement universel d’une commande

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("Indiquez une commande à exécuter, par exemple: java RunCommand ls -l");
            return;
        }

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

        // Nous lançons des threads séparés pour lire stdout et stderr
        var t1 = Thread.ofPlatform().start(() -> readStream(process.inputReader(), "[stdout]"));
        var t2 = Thread.ofPlatform().start(() -> readStream(process.errorReader(), "[stderr]"));

        int exitCode = process.waitFor(); // nous attendons la fin du processus
        t1.join();
        t2.join();

        System.out.println("Le processus s’est terminé avec le code: " + 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("Erreur de lecture " + prefix + ": " + e.getMessage());
        }
    }
}

Cette approche permet d’éviter les deadlocks et de ne perdre aucune erreur !

6. En bref sur les encodages

Par défaut, InputStreamReader utilise l’encodage du système. Si le processus externe écrit dans un autre encodage (par exemple UTF-8 alors que vous êtes en Windows-1251), la sortie peut apparaître avec des caractères illisibles. Indiquez l’encodage explicitement :

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

7. Exemple: lancer un script Python et lui transmettre des données

Supposons que vous ayez un script Python qui attend plusieurs lignes en entrée, puis affiche le résultat.

adder.py :

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

Depuis Java, vous pouvez lui envoyer des nombres et lire la réponse :

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

        // Nous envoyons deux nombres sur le stdin du script Python
        try (var writer = process.outputWriter(StandardCharsets.UTF_8)) {
            writer.println("10");
            writer.println("25");
        }

        // Nous lisons le résultat depuis stdout à l’aide de inputReader
        try (BufferedReader reader = process.inputReader(StandardCharsets.UTF_8)) {
            reader.lines().forEach(line -> System.out.println("Réponse de Python: " + line));
        }

        int exitCode = process.waitFor();
        System.out.println("Python s’est terminé avec le code: " + exitCode);
    }
}

8. Pourquoi un deadlock? Comment l’éviter

Un deadlock est une situation où le processus et votre programme s’attendent mutuellement indéfiniment. Par exemple, si le tampon de sortie du processus est plein et que vous ne lisez pas ce flux, le processus s’arrête. Si vous attendez la fin du processus alors qu’il attend que vous lui écriviez quelque chose — deadlock également.

Comment l’éviter :

  • Lisez toujours les deux flux (stdout et stderr).
  • Pour les processus longs/verbeux, utilisez des threads séparés pour la lecture.
  • Ne bloquez pas l’écriture et la lecture simultanément dans un même thread.

9. Erreurs typiques lors de l’utilisation des flux d’entrée/sortie d’un processus

Erreur n° 1: Lire un seul flux (stdout ou stderr).
Si vous ne lisez que stdout alors que le processus écrit une erreur dans stderr, le tampon d’erreurs peut se remplir — le processus se bloquera. Solution : lire les deux flux (de préférence dans des threads séparés).

Erreur n° 2: Mauvais encodage.
Si vous n’indiquez pas l’encodage adéquat, la sortie peut être illisible. Vérifiez toujours l’encodage utilisé par le processus externe (par exemple, spécifiez explicitement "UTF-8" ou utilisez StandardCharsets.UTF_8).

Erreur n° 3: Flux non fermés.
Des flux laissés ouverts peuvent entraîner des fuites de ressources. Utilisez try-with-resources ou fermez explicitement tous les flux.

Erreur n° 4: Oublier d’appeler flush() lors de l’écriture dans le stdin du processus.
Si vous oubliez d’appeler flush(), les données peuvent ne jamais atteindre le processus — il attendra l’entrée indéfiniment.

Erreur n° 5: Différences entre plateformes.
Les commandes et la syntaxe peuvent différer entre Windows et Linux/Mac. Vérifiez l’OS via System.getProperty("os.name") et adaptez les commandes en conséquence.

Erreur n° 6: Exceptions non gérées.
Le travail avec des processus s’accompagne souvent d’IOException. Gérez toujours les exceptions afin d’éviter un arrêt brutal du programme.

1
Mission
JAVA 25 SELF, niveau 61, leçon 1
Bloqué
Message secret 📩
Message secret 📩
1
Mission
JAVA 25 SELF, niveau 61, leçon 1
Bloqué
Contrôleur de la station spatiale 🚀
Contrôleur de la station spatiale 🚀
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION