2010-09-04 31 views
2

J'essaie d'exécuter un nouveau processus et de lire son flux d'entrée en Java. J'ai utilisé Runtime.getRuntime(). Exec (String) avec succès pour démarrer et recevoir l'entrée de plusieurs processus. Cependant, lorsque j'essaie d'utiliser exec sur d'autres processus, la méthode de lecture du flux d'entrée se bloque et il semble qu'il n'y ait pas d'entrée. Qu'est-ce qui pourrait rendre le flux d'entrée vide pour certains de ces processus? Plus précisément, je me demande pourquoi bash.exe ne produit rien.Runtime.getRuntime(). Exec ("C: cygwin bin bash.exe") n'a pas d'entrée à lire

J'ai écrit un test JUnit pour démontrer cette question:

import java.io.IOException; 
import java.io.InputStream; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.List; 

import junit.framework.TestCase; 

public class TestExec extends TestCase { 

    public void testExec() throws IOException { 
     List<InputPrinter> threads = new ArrayList<InputPrinter>(); 

     // Create a process for each of the commands and make sure that 
     // it outputs at least one line to its input stream. 
     threads.add(testExec("cmd")); 
     threads.add(testExec("java")); 
     threads.add(testExec("C:/cygwin/bin/vim-nox.exe")); 

     // These bottom two fail, even though executing these 
     // commands in cmd.exe results in immediate output 
     threads.add(testExec("javac")); 
     threads.add(testExec("C:/cygwin/bin/bash.exe")); 

     // Give the threads a second to execute 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
      fail(); 
     } 

     // Test that each command had input to read 
     for(InputPrinter ip : threads) { 
      assertTrue(ip.command + " has not read any input", ip.hasRead); 
     } 
    } 

    // Starts a process for the given command and returns an 
    // InputPrinter that can be used to check if the process 
    // has had an input to read. 
    public InputPrinter testExec(String command) throws IOException { 
     Process proc = Runtime.getRuntime().exec(command); 
     InputStream in = proc.getInputStream(); 

     InputPrinter ip = new InputPrinter(in, command); 
     new Thread(ip).start(); 

     return ip; 
    } 

    // Simple Runnable to read from an InputStream. hasRead will be 
    // true if at least one input has been read from the stream 
    private class InputPrinter implements Runnable { 
     InputStream in; 
     String command; 
     boolean hasRead; 

     public InputPrinter(InputStream in, String command) { 
      this.in = in; 
      this.command = command; 
      this.hasRead = false; 
     } 

     // Loop indefinitely while printing any received input 
     public void run() { 
      try { 
       final byte[] b = new byte[1024]; 
       while (true) { 
        int n = in.read(b); 
        if (n > 0) { 
         System.out.print(new String(Arrays.copyOf(b, n))); 
         hasRead = true; 
        } 
       } 
      } catch (IOException e) { 
       e.printStackTrace(); 
       fail(); 
      } 
     } 
    } 

} 

EDIT:

Pour autant que je sache, si un programme n'utilise pas stdout ou stderr, je ne devrais pas voir quelque chose dans l'invite de commande Windows. Ce que je me attends de voir quand je commence le processus de bash est « bash-3.2 $, » la même chose que je vois quand j'ouvrir l'invite de commande et exécutez « bash.exe »:

Microsoft Windows [Version 6.1.7600] 
Copyright (c) 2009 Microsoft Corporation. All rights reserved. 

C:\cygwin\bin>bash.exe 
bash-3.2$ 
+0

C'est génial de voir des gens en utilisant des assertions plutôt que 'System.out' en questions –

+0

Je pense que c'est parce que le programme fait une impression au lieu d'un println .. – Patrick

+0

Quelle est la question? Quel est le problème ? –

Répondre

2

Indépendamment de Java, pour autant que je sache, vous pouvez rediriger la sortie (ou l'entrée) de/vers bash uniquement lorsqu'elle s'exécute en tant que script, et non lorsqu'elle s'exécute en tant que shell interactif (auquel cas vous ne pouvez lui transmettre que des paramètres cmd). En d'autres termes, lorsque vous exécutez bash à partir de cmd comme vous le mentionnez dans le commentaire, vous voyez une sortie, mais elle est contenue dans le processus bash, elle n'est pas sortie que bash renvoie au processus cmd parent. En ce qui concerne le processus javac, il envoie réellement la sortie au flux d'erreurs. Essayez de courir à partir de cmd javac 1>null et javac 2>null et vous verrez la différence. Avez-vous regardé l'API here? Vous pouvez essayer d'utiliser ProcessBuilder et rediriger le flux d'erreurs vers le flux d'entrée principal, il sera beaucoup plus facile de travailler avec les processus de cette façon.

+0

Si ce que vous avez dit à propos de bash est vrai, alors pourquoi puis-je lancer le bash.Exe exécutable à partir de l'invite de commande? (voir la question pour les modifications) – peskal

+0

parce que bash s'exécute en mode interactif de sorte qu'il prend en charge la fenêtre cmd. Il apparaît seulement comme s'il écrivait sur stdout. Essayez d'exécuter 'edit' à partir de cmd, par exemple, il s'agit d'une application native ms-dos. Il change complètement l'écran, et pourtant c'est seulement une application textuelle, pas graphique – Yoni

+0

Hm, il semble que je ne comprenne pas quelque chose sur la manière dont les processus alimentent les interpréteurs en ligne de commande. J'ai posté une nouvelle question: http://stackoverflow.com/questions/3645040/how-do-command-line-interpreters-work – peskal

2

Un processus a généralement non pas un mais deux flux de sortie qui lui sont associés. Ce sont:

  1. stdout, qui peut être lu avec getInputStream()
  2. stderr, qui peut être lu avec getErrorStream()

Javac écrit à stderr, pas stdout, de sorte que vous n » t lire sa sortie. Parce qu'il n'est pas pratique d'avoir à les lire tous les deux (il y a quelques années, j'ai dû écrire un thread supplémentaire pour cela), ils ont introduit une nouvelle API pour les processus système, à savoir ProcessBuilder, qui permet de rediriger stderr vers stdout.

Il suffit de remplacer les lignes

Process proc = Runtime.getRuntime().exec(command); 
    InputStream in = proc.getInputStream(); 

avec

ProcessBuilder pb = new ProcessBuilder(command); 
    pb.redirectErrorStream(true); 
    Process proc = pb.start(); 

, ajoutez les importations nécessaires, et réussit votre test :).

+0

Merci, n'a même pas pensé à ça! Malheureusement, j'ai toujours des problèmes avec bash.exe ... peut-être en raison de mon malentendu sur la façon dont le programme fonctionne bien. – peskal