web-dev-qa-db-fra.com

Java Processus avec flux d'entrée / sortie

J'ai l'exemple de code suivant ci-dessous. Vous pouvez ainsi entrer une commande dans le shell bash, à savoir echo test et que le résultat soit renvoyé. Cependant, après la première lecture. Les autres flux de sortie ne fonctionnent pas?

Pourquoi est-ce 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 périodiquement une commande dans/bash afin que OutputStream et InputStream doivent fonctionner en tandem et ne pas cesser de fonctionner. J'ai aussi rencontré l'erreur 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);
}
89
James moore

Tout d'abord, je recommanderais 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 de processus externes. À mon avis, l’amélioration la plus importante par rapport à Runtime.getRuntime().exec() est qu’il vous permet de rediriger l’erreur standard de la processus enfant dans sa sortie standard. Cela signifie que vous n’avez qu’un seul InputStream à lire. Avant cela, vous deviez avoir deux threads distincts, un en lecture de stdout et un en lecture de stderr, pour éviter le remplissage du tampon d'erreur standard alors que le tampon de sortie standard était vide (provoquant le blocage du processus enfant), ou inversement.

Ensuite, les boucles (dont vous avez deux)

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

ne quitte que lorsque reader, qui lit à partir de la sortie standard du processus, retourne la fin du fichier. Cela ne se produit que lorsque le processus bash se termine. Il ne renverra pas la fin du fichier si, à l'heure actuelle, il n'y a plus de sortie du processus. Au lieu de cela, il attendra la prochaine ligne de sortie du processus et ne reviendra pas jusqu'à ce qu'il ait cette ligne suivante.

Puisque vous envoyez deux lignes d’entrée au processus avant d’atteindre cette boucle, la première de ces deux boucles s’accroche si le processus ne s’est pas arrêté après ces deux lignes d’entrée. Il attendra 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, alors j'ai remplacé /bin/bash Par cmd.exe, Mais les principes devraient être les mêmes), et j'ai découvert que:

  • après avoir tapé sur deux lignes, la sortie des deux premières commandes apparaît, mais le programme se bloque,
  • si je tape dans, disons, echo test, puis exit, le programme sort de la première boucle depuis la fin du processus cmd.exe. Le programme demande ensuite une autre ligne d'entrée (qui est ignorée), passe directement à la seconde boucle car le processus enfant s'est déjà terminé, puis se ferme.
  • si je tape exit puis echo test, je reçois une exception IOException se plaignant de la fermeture d'un tuyau. C'était à prévoir - la première ligne d'entrée a entraîné la fermeture du processus et il n'y a nulle part où envoyer la deuxième ligne.

J'ai vu un truc qui ressemble à ce que vous semblez vouloir dans un programme sur lequel je travaillais auparavant. Ce programme a conservé un certain nombre de shells, y a exécuté des commandes et en a lu le résultat. Le truc utilisé était de toujours écrire une ligne "magique" qui marque la fin de la sortie de la commande Shell et de 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 tout remplacé après la ligne qui attribue à 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 cela, je pouvais exécuter de manière fiable quelques commandes et recevoir la sortie de chacune d’elles individuellement.

Les deux commandes echo --EOF-- De la ligne envoyée au shell permettent de s'assurer que la sortie de la commande se termine par --EOF--, Même en cas d'erreur de la commande.

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

  • 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,
  • cela devient un peu confus si la commande exécutée par le shell écrit une ligne --EOF--.
  • bash signale une erreur de syntaxe et se ferme si vous saisissez du texte avec un ) sans correspondance.

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

EDIT : améliore la gestion des sorties et autres modifications mineures après son exécution sous Linux.

134
Luke Woodward

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 du thread principal afin que vous puissiez lire et écrire en même temps. Vous pouvez modifier votre programme de la manière suivante:

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 vous pouvez lecteur sera le même que ci-dessus c'est à dire.

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

faites votre rédacteur en final, sinon il ne sera pas accessible par classe intérieure.

4
Shashank Jain

Vous avez writer.close(); dans votre code. Donc bash reçoit EOF sur son stdin et quitte. Ensuite, vous obtenez Broken pipe lorsque vous essayez de lire le stdoutde la bash défunte.

1
gpeche