97 votes

Processus Java avec flux d'entrée/sortie

J'ai l'exemple de code suivant. Par lequel vous pouvez entrer une commande au shell bash, c'est-à-dire echo test et obtenir le résultat en retour. Cependant, après la première lecture. Les autres flux de sortie ne fonctionnent pas ?

Pourquoi est-ce le cas ou est-ce que je fais quelque chose de mal ? Mon objectif final est de créer une tâche planifiée Threaded qui exécute une commande périodiquement vers /bash afin que l'utilisateur puisse se connecter à l'ordinateur. OutputStream y InputStream devraient travailler en tandem et ne pas s'arrêter de travailler. J'ai également rencontré l'erreur suivante java.io.IOException: Broken pipe Des idées ?

Merci.

String line;
Scanner scan = new Scanner(System.in);

Process process = Runtime.getRuntime ().exec ("/bin/bash");
OutputStream stdin = process.getOutputStream ();
InputStream stderr = process.getErrorStream ();
InputStream stdout = process.getInputStream ();

BufferedReader reader = new BufferedReader (new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

String input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.close();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

0 votes

"Broken pipe" signifie probablement que le processus enfant a quitté. Je n'ai pas entièrement examiné le reste de votre code pour voir quels sont les autres problèmes.

1 votes

Utilisez des fils séparés, cela fonctionnera très bien.

144voto

Luke Woodward Points 20417

Tout d'abord, je recommande de remplacer la ligne

Process process = Runtime.getRuntime ().exec ("/bin/bash");

avec les lignes

ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();

ProcessBuilder est nouveau dans Java 5 et facilite l'exécution des processus externes. À mon avis, son amélioration la plus significative par rapport à Runtime.getRuntime().exec() est qu'il vous permet de rediriger l'erreur standard du processus enfant vers sa sortie standard. Cela signifie que vous n'avez qu'un seul InputStream à lire. Avant cela, vous deviez avoir deux Threads séparés, l'un lisant à partir de stdout et une lecture de stderr afin d'éviter que le tampon d'erreur standard ne se remplisse alors que le tampon de sortie standard est vide (provoquant le blocage du processus enfant), ou vice versa.

Ensuite, les boucles (dont vous avez deux)

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

ne sortent que lorsque le reader qui lit la sortie standard du processus, retourne la fin du fichier. Cela ne se produit que lorsque le bash le processus sort. Il ne retournera pas la fin du fichier s'il n'y a plus de sortie du processus. Au lieu de cela, elle attendra la prochaine ligne de sortie du processus et ne reviendra pas avant d'avoir obtenu cette ligne.

Puisque vous envoyez deux lignes d'entrée au processus avant d'atteindre cette boucle, la première de ces deux boucles se bloquera si le processus n'est pas sorti après ces deux lignes d'entrée. Il restera là à attendre qu'une autre ligne soit lue, mais il n'y aura jamais d'autre ligne à lire.

J'ai compilé votre code source (je suis sous Windows pour le moment, donc j'ai remplacé /bin/bash avec cmd.exe mais les principes devraient être les mêmes), et j'ai trouvé que :

  • après avoir tapé deux lignes, la sortie des deux premières commandes apparaît, mais le programme se bloque,
  • si je tape, disons, echo test et ensuite exit le programme sort de la première boucle, puisque l'option cmd.exe a quitté le processus. Le programme demande alors une autre ligne d'entrée (qui est ignorée), passe directement à la deuxième boucle puisque le processus enfant est déjà sorti, puis se termine lui-même.
  • si je tape dans exit et ensuite echo test j'obtiens une IOException se plaignant de la fermeture d'un tube. C'est normal - la première ligne d'entrée a provoqué la sortie du processus, et il n'y a nulle part où envoyer la deuxième ligne.

J'ai vu une astuce qui fait quelque chose de similaire à ce que vous semblez vouloir, dans un programme sur lequel je travaillais. Ce programme conservait un certain nombre de shells, exécutait des commandes dans ceux-ci et lisait la sortie de ces commandes. L'astuce utilisée consistait à toujours écrire une ligne "magique" qui marque la fin de la sortie de la commande shell, et à l'utiliser pour déterminer quand la sortie de la commande envoyée au shell était terminée.

J'ai pris votre code et j'ai remplacé tout ce qui se trouve après la ligne qui assigne à writer avec la boucle suivante :

while (scan.hasNext()) {
    String input = scan.nextLine();
    if (input.trim().equals("exit")) {
        // Putting 'exit' amongst the echo --EOF--s below doesn't work.
        writer.write("exit\n");
    } else {
        writer.write("((" + input + ") && echo --EOF--) || echo --EOF--\n");
    }
    writer.flush();

    line = reader.readLine();
    while (line != null && ! line.trim().equals("--EOF--")) {
        System.out.println ("Stdout: " + line);
        line = reader.readLine();
    }
    if (line == null) {
        break;
    }
}

Après avoir fait cela, j'ai pu exécuter quelques commandes de manière fiable et obtenir les résultats de chacune d'entre elles individuellement.

Les deux echo --EOF-- dans la ligne envoyée au shell sont là pour s'assurer que la sortie de la commande est terminée par --EOF-- même à la suite d'une erreur de la commande.

Bien sûr, cette approche a ses limites. Ces limites comprennent :

  • si j'entre une commande qui attend une entrée utilisateur (par exemple un autre shell), le programme semble se bloquer,
  • il suppose que chaque processus exécuté par le shell termine sa sortie par une nouvelle ligne,
  • il s'embrouille un peu si la commande exécutée par le shell écrit une ligne --EOF-- .
  • bash signale une erreur de syntaxe et se termine si vous entrez du texte avec une erreur de correspondance. ) .

Ces points peuvent ne pas avoir d'importance pour vous si ce que vous envisagez d'exécuter en tant que tâche planifiée est limité à une commande ou à un petit ensemble de commandes qui ne se comporteront jamais de manière aussi pathologique.

EDIT : amélioration de la gestion des sorties et autres changements mineurs suite à l'exécution sous Linux.

0 votes

Merci pour cette réponse complète. Cependant, je pense avoir identifié le véritable cas de mes problèmes. C'est ce que vous m'avez fait remarquer dans votre message. stackoverflow.com/questions/3645889/ . Merci.

1 votes

Remplacer /bin/bash par cmd n'a pas vraiment le même comportement, car j'ai fait un programme qui fait la même chose mais qui a des problèmes avec bash. Dans mon cas, l'ouverture de trois threads distincts pour chaque entrée/sortie/erreur fonctionne mieux sans aucun problème pour les commandes interactives de longue durée.

0 votes

5voto

Shashank Jain Points 31

Je pense que vous pouvez utiliser un thread comme demon-thread pour lire votre entrée et votre lecteur de sortie sera déjà dans la boucle while dans le thread principal afin que vous puissiez lire et écrire en même temps.Vous pouvez modifier votre programme comme ceci :

Thread T=new Thread(new Runnable() {

    @Override
    public void run() {
        while(true)
        {
            String input = scan.nextLine();
            input += "\n";
            try {
                writer.write(input);
                writer.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }
} );
T.start();

et le lecteur sera le même que ci-dessus, à savoir

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

faites en sorte que votre écrivain soit final, sinon il ne sera pas accessible par les classes internes.

1voto

gpeche Points 8596

Vous avez writer.close(); dans votre code. Ainsi, bash reçoit EOF sur son stdin et les sorties. Ensuite, vous obtenez Broken pipe lors de la tentative de lecture du stdout de la défunte bash.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X