Runtime.exec() n’est pas des plus simple…


L’API Java est assez imposante et complète, mais elle contient toutefois un certain nombre de classe et/ou méthode qui ne sont pas des plus simple à utiliser, ou qui ont un comportement très éloigné de ce que l’on peut trouver dans d’autre langage… ce qui les rends plus complexe et énigmatique.

Si on prend comme exemple le langage C : on peut utiliser les différentes fonctions exec() pour exécuter un programme externe, ou directement la fonction system() pour appeler une commande shell.

Ces fonctions se retrouvent généralement dans la plupart des langages…

Les developpeurs Java connaissent surement l’existence des méthodes Runtime.exec() dont l’objectif est d’exécuter un programme externe représenté par la classe Process. La classe ProcessBuilder de Java 5.0 est peut-être moins connu, mais elle permet grosso-modo la même chose (mais en adoptant une approche un peu plus ‘OO‘).

A première vue on pourrait croire que l’on a affaire à des fonctions/méthodes très similaire, mais il existe pourtant une grosse différence entre l’exécution de programme externe en Java et en C : la gestion des flux d’entrées/sorties !

En effet, lorsqu’on lance un programme externe avec les fonctions exec()/system() du C, les flux d’entrés/sorties du nouveau programme (stdin, stdout et stderr) sont partagés avec le programme appelant. C’est extrêmement pratique puisque les programmes appelés depuis une ligne de commande peuvent interagir directement avec cette dernière.

Or, ce qui marque lorsqu’on lance pour la première fois un Process depuis un programme Java, c’est justement que rien ne semble se passer : la console reste désespérément muette ! En effet ils ne partagent pas leurs flux d’entrés/sorties avec le programme appelant, et ils ne peuvent donc pas lire/écrire directement sur la console.

Au lieu de cela les entrés/sorties standards des Process sont redirigées vers trois flux (getOutputStream(), getInputStream() et getErrorStream()) accessibles par le programme Java.

Et cela pose plusieurs problèmes :

  • Premièrement, l’API n’est pas des plus claire à cause de l’inversion des flux : par exemple getOutputStream() renvoi un OutputStream permettant d’écrire dans l’entrée standard du Process, et getInputStream() renvoi un InputStream permettant de lire la sortie standard…
  • Secondo, les redirections d’E/S utilisent des buffers de taille limité (et dépendant du système hôte). Si les flux d’E/S ne sont pas traité par le programme appelant, le processus peut se retrouver bloqué. Pire encore : on peut facilement se retrouver dans un cas d’inter-blocage (le processus attend que le programme Java vide le buffer du flux afin de pouvoir continuer son exécution, alors que le programme Java attend que le processus fils se termine pour continuer son exécution, et donc les deux application s’attendent mutuellement).
  • Enfin, et toujours pour éviter des inter-blocages, les différents flux doivent être traitées depuis des threads différents, ce qui vient encore compliquer le tout.

Bref : avec comme simple objectif de vouloir lancer un programme, on se retrouve à devoir gérer des lectures de flux et du multithreading…

Un programme n’est pas une ligne de commande

De plus, il faut bien prendre en compte que les méthodes exec() de Java, tout comme les fonctions exec() du C, permettent de lancer un programme, mais pas d’interpréter une ligne de commande ! C’est à dire que le programme appelé doit correspondre à un fichier exécutable, et que chacun des paramètres lui seront passés tel quel sans modification.

En effet, l’interprétation des lignes de commandes fait partie des fonctions du shell, et s’il est possible de l’appeler facilement grâce à la fonction system() du C, il faut malheureusement constater qu’il n’existe aucune équivalence en standard en Java !

Pourtant le shell apporte de nombreux avantages puisqu’il permet d’exécuter des commandes bien plus élaboré :

  • La gestion des redirections avec <, >, >>, 2>, 2>> et 2>&1.
  • La gestion des pipes de processus avec | et des opérateurs booléens && et ||.
  • La gestion des résolutions des variables d’environnements selon la norme du système (%NAME% sous Windows et $NAME sous les systèmes Unix).
  • La gestion des commandes builtins du shell (echo, cd>, etc.).
  • Et plus globalement de toutes les spécificités du shell du système d’exploitation hôte, comme l’interprétation des meta-caractères des shell Unix (*, ?, \, etc.)

Bien sûr, la raison est toute simple : l’appel de commande du shell gênerait à la portabilitée de l’application (il faudrait gérer des commandes différentes selon le système cible).

Mais si l’intention est louable, elle est également très problématique car l’appel du moindre programme natif en Java peut devenir un vrai calvaire (ou presque).

Comment faire la même chose en Java ?

Pour arriver à faire la même chose que les fonctions exec() ou system() du C, il faut prendre en compte les spécificité de l’exécution d’application depuis Java, c’est à dire :

  • Chaque flux doit être traité, c’est à dire que le flux d’entrée doit recevoir des données puis être fermé, et les flux de sortie doivent être lu assez rapidement pour éviter de bloquer le buffer. Le code n’est pas bien compliqué, mais il faut avouer qu’il est assez « pénible » à écrire, surtout à cause du fait que ces lectures/écritures doivent être effectué depuis des threads différents. On peut toutefois simplifier cela en fermant directement les flux qui ne sont pas utilisés…
  • On ne peut pas évaluer des lignes de commandes directement, mais il est possible d’appeler le programme représentant le shell système pour qu’il les évalue. Le problème étant que ce même shell dépend du système, c’est à dire par défaut command.com sous les Windows 9x, cmd.exe sous les Windows NT, /bin/sh sous les systèmes Unix et assimilé. Mais il peut également y avoir des shells « personnalisé », généralement via les variables d’environnements %ComSpec% sous Windows, ou $SHELL sous Unix
    Ces shells acceptent tous un paramètre /C (sous Windows) ou -c (sous Unix) qui permet d’interpréter une ligne de commande complexe.

Encore une fois ce n’est pas bien compliqué, si ce n’est qu’il faut déterminer le shell système pour tenter d’avoir un tant soit peu de portabilité (même si l’appel de programme externe ou de ligne de commande nuit déjà à la portabilité).

Pour plus de détail concernant l’exécution de programme externe, je vous invite à consulter ce tutoriel : Exécuter une application externe en Java.

Mais il faut avouer que c’est quand même un peu casse pied de devoir faire tout cela rien que pour lancer une application ou une ligne de commande, c’est pourquoi j’ai intégré tout cela dans une API un peu plus simple.

Shell : Une API système pour Java

Voici donc l’API Shell qui permet de simplifier l’exécution de programme et de ligne de commande depuis Java. Elle comporte principalement deux classes :

  • ProcessConsumer permet de traiter les flux d’entrées/sorties d’un processus en lui associant des flux via les méthodes input(), output() et error(). Elle dispose ensuite de méthodes permettant de lancer le traitement des flux (consume(), consumeInBackground()) voir d’effectuer le traitement en récupérant la sortie standard en tant que chaine de caractères (consumeAsString(), consumeAsStringInBackground()).

    Mais comme un exemple vaut plus qu’un long discourt, pour utiliser des fichiers comme flux d’entrées/sorties d’un process, on pourrait utiliser ceci :

    
    
      new ProcessConsumer(process)
        .input( new FileInputStream("in.txt") )
        .output( new FileOutputStream("out.txt") )
        .error( new FileOutputStream("err.txt") )
        .consume();

    La méthode consume() se charge de rediriger les flux vers le process, et de les fermer proprement à la fin du processus.

    De plus, lorsque les flux de sortie standard et d’erreur ne sont pas spécifié, ils sont redirigés respectivement vers System.out et System.err. Ainsi, ce code affichera le résultat directement sur la console :

    
    
      new ProcessConsumer(process)
        .input( new FileInputStream("in.txt") )
        .consume();

    Par contre il n’en est pas de même pour le flux d’entrée, car lorsqu’on utilise System.in en tant que source de données, le thread de lecture se retrouve bloquer si l’utilisateur n’effectue aucune saisie. A ma connaissance, il n’y a pas de moyen de contourner cela étant donné que System.in n’est pas « interruptible ». Ainsi pour le moment l’utilisation de System.in comme source de données pour le flux d’entrée du process provoquera une exception. Par défaut aucun flux d’entrée n’est utilisé.

  • La classe Shell quand à elle se concentre plus sur l’interaction avec le système. En plus de quelques accesseurs/mutateurs permettant de manipuler des propriétés basiques (répertoire d’exécution, charset, variables d’environnement utilisateur), elle dispose principalement de deux méthodes :
    • command(String) permet d’exécuter une ligne de commande via le shell système. Cette méthode retourne une instance de ProcessConsumer correctement initialisé qu’il suffit d’utiliser.

      Par exemple pour récupérer en tant que chaine de caractère le retour de la commande ls | sort (sous un système Unix), il suffit d’utiliser le code suivant :

      
      
        Shell sh = new Shell();
       
        String result = sh.command("ls | sort").consumeAsString();
    • exec() quand à lui fonctionne de la même manière que la méthode Runtime.exec(), et attend donc un programme valide et non pas une ligne de commande. Elle a toutefois l’avantage de retourner un objet de type ProcessConsumer :
      
      
        String result = sh.exec("monApplicationNative",
          "param1", "param2").consumeAsString();

    Pour finir, il faut noter l’existence de la méthode statique Shell.system(String) qui permet de lancer une commande en redirigeant directement sa sortie dans la console.

Cette librairie est encore jeune, mal-documenté, risque d’être encore modifiée et doit surement comporter certains bugs. Elle n’a été testé que sous Windows XP et sous Ubuntu Linux mais devrait être compatible avec la plupart des système Windows et Unix (merci de me contacter si ce n’est pas le cas).

Téléchargement

Cette librairie est disponible sous licence CeCILL-C directement sur ces adresses :

Enfin, pour information, l’utilisation d’une librairie sous licence CeCILL-C n’a aucune incidence sur la licence de votre application :

Lorsque vous distribuez une application incorporant des logiciels régis par CeCILL-C, vous devez mettre à disposition de la communauté et soumettre à cette licence les modifications que vous avez apportées au code source des logiciels sous CeCILL-C; cependant vous gardez la liberté de choisir une autre licence, libre ou propriétaire, pour le reste de votre application.

37 réflexions au sujet de « Runtime.exec() n’est pas des plus simple… »

  1. tutur50000

    Pour le moment en local, j’effectue ceci :

    String[] command = { « cmd.exe », « /C », « Start », ShellFile.getAbsolutePath() };
    Shell sh = new Shell(); sh.exec(command).consume();

    Mais au final, j’aimerais que la commande soit l’execution d’un fichier .sh (jusque là ça va) et où dans ce fichier, il y ait des commandes ainsi que des lancements de fichiers perl.

    Cela ne pose pas de problème ? Au niveau de l’ordonnancement ?

    En tout cas très belle API ;-)

  2. Otacon87

    Bonjour,

    Merci pour ton API !!
    J’aurais une petite question, enfin plutôt un problème.
    J’ai utilisé ton API, pour mon programme, une fois terminé je fais un jar de mon programme, et là quand je lance mon jar, j’obtiens l’erreur suivante :
    Exception in thread « main » java.lang.NoClassDefFoundError: com/developpez/adigub
    a/shell/Shell

    Saurais tu d’où ça viens ?

    Merci

  3. Avatar de Saif_24Saif_24

    Salut!!
    je te felécite pour ton API!Mais j’ai eu un petie defficulté!!
    ben,je veux bien lance des commande en tant que root! alors j’essaye la chose suivant:
    package Shell;

    import com.developpez.adiguba.shell.Shell;
    import java.io.*;

    public class TestCmd {

    public static void test() throws IOException{
    Shell sh = new Shell();
    String result = sh.command(« sudo snort -c /etc/snort/snort.conf »).consumeAsString();
    System.out.print(result);
    }

    public static void main(String[] args) throws IOException{
    TestCmd.test();
    }
    }

    Mais il m’affiche l’errur suivant:
    sudo: no tty present and no askpass program specified

    Svp tu as une idée comment le reglé?
    Merci une autre fois!

  4. mOuLi

    Bonjour,

    En ce qui concerne les redirections (que ce soit avec le ProcessBuilder ou avec API) : est-ce que le principe reste valable si la commande qu’on invoque correspond à un programme Java utilisant java.io.Console (et pas System.out) ? J’ai plutôt l’impression que non

  5. Fused

    Merci bien, cela fonctionne à merveille !

    J’avais trouvé une solution avec un JTextField en redirigeant System.out dedans, mais ça ne le mettait pas à jour en temps réel, j’aime beaucoup ta solution « Appendable ».

    Pour le charset, je n’avais essayé que UTF-8 et ISO Latin, merci pour l’astuce !

  6. adiguba Auteur de l’article

    Pour l’encodage c’est un problème de Windows qui utilise deux encodages différents selon le type d’application. Sous les systèmes européens on a généralement du windows-1252 (iso-latin1 modifié) et du cp850 (utilisé par les applications consoles).

    Le problème est que l’encodage par défaut est windows-1252, et que les applications console utilisent le cp850. Du coup les données ne sont pas lu dans le bon encodage et on se retrouve des trucs bizarres sur les accents…

    Il faut donc forcer l’utilisation de cet encodage pour la lecture des flux du process :

    &nbsp;&nbsp;Shell shell = new Shell();&nbsp;<br />
    &nbsp;&nbsp;shell.setCharset("cp850");&nbsp;<br />
    &nbsp;&nbsp;shell.command("ping www.google.com").consume();

    Mais attention car c’est spécifique à Windows !

    Sinon pour la redirection vers un JTextField, je pense que tu voulais plutôt parler de rediriger vers un JTextPane ou un JTextArea, non ?
    Il ne faut pas passer par ProcessConsumer qui ne devrait être utilisé QUE pour gérer des process qui ne serait pas lancé par la classe Shell.

    En fait il suffit d’implémenter ton propre objet « Appendable », qui recevra les données lu et les ajoutera au composant textuelle. Par exemple :

    public class AppendableJTextComponent implements Appendable {&nbsp;<br />
    &nbsp;&nbsp;<br />
    &nbsp;&nbsp;private final JTextComponent component;&nbsp;<br />
    &nbsp;&nbsp;<br />
    &nbsp;&nbsp;public AppendableJTextComponent(JTextComponent comp) {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;this.component = comp;&nbsp;<br />
    &nbsp;&nbsp;}&nbsp;<br />
    &nbsp;<br />
    &nbsp;&nbsp;@Override&nbsp;<br />
    &nbsp;&nbsp;public Appendable append(char c) throws IOException {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;appendStringOnEDT(Character.toString(c));&nbsp;<br />
    &nbsp;       return this;&nbsp;<br />
    &nbsp;&nbsp;}&nbsp;<br />
    &nbsp;<br />
    &nbsp;&nbsp;@Override&nbsp;<br />
    &nbsp;&nbsp;public Appendable append(CharSequence csq, int start, int end)&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throws IOException {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;appendStringOnEDT(csq.subSequence(start, end).toString());&nbsp;<br />
    &nbsp;       return this;&nbsp;<br />
    &nbsp;&nbsp;}&nbsp;<br />
    &nbsp;<br />
    &nbsp;&nbsp;@Override&nbsp;<br />
    &nbsp;&nbsp;public Appendable append(CharSequence csq) throws IOException {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;appendStringOnEDT(csq.toString());&nbsp;<br />
    &nbsp;       return this;&nbsp;<br />
    &nbsp;&nbsp;}&nbsp;<br />
    &nbsp;&nbsp;<br />
    &nbsp;&nbsp;private void appendStringOnEDT(final String str) {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;if (SwingUtilities.isEventDispatchThread()) {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Document doc = component.getDocument();&nbsp;<br />
    &nbsp;&nbsp;        if (doc != null) {&nbsp;<br />
    &nbsp;&nbsp;            try {&nbsp;<br />
    &nbsp;&nbsp;                doc.insertString(doc.getLength(), str, null);&nbsp;<br />
    &nbsp;&nbsp;            } catch (BadLocationException e) {&nbsp;<br />
    &nbsp;&nbsp;            &nbsp;&nbsp;throw new RuntimeException(e);&nbsp;<br />
    &nbsp;&nbsp;            }&nbsp;<br />
    &nbsp;&nbsp;        }&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;} else {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SwingUtilities.invokeLater(new Runnable() {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public void run() {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;appendStringOnEDT(str);&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<br />
    &nbsp;&nbsp;}&nbsp;<br />
    }&nbsp;<br />

    Ensuite il suffit d’utiliser cet appendable comme output :

    &nbsp;<br />
    &nbsp;&nbsp;final JTextPane textpane = new JTextPane();&nbsp;<br />
    &nbsp;&nbsp;JButton button = new JButton("ping");&nbsp;<br />
    &nbsp;&nbsp;button.addActionListener(new ActionListener() {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;public void actionPerformed(ActionEvent e) {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new SwingWorker() {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;protected Integer doInBackground() throws Exception {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Shell shell = new Shell();&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shell.setCharset("cp850");&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return shell.command("ping www.google.com").output(new AppendableJTextComponent(textpane)).consume();&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}.execute();&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<br />
    &nbsp;&nbsp;});&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;<br />
    &nbsp;&nbsp;JFrame frame = new JFrame("Demo");&nbsp;<br />
    &nbsp;&nbsp;frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);&nbsp;<br />
    &nbsp;&nbsp;frame.setSize(400,400);&nbsp;<br />
    &nbsp;&nbsp;frame.setLocationRelativeTo(null);&nbsp;<br />
    &nbsp;&nbsp;frame.getContentPane().add(new JScrollPane(textpane));&nbsp;<br />
    &nbsp;&nbsp;frame.getContentPane().add(button, BorderLayout.SOUTH);&nbsp;<br />
    &nbsp;&nbsp;frame.setVisible(true);&nbsp;<br />

    a++

  7. Fused

    Merci pour cet API très intéressante.

    Un exemple pour rediriger les sorties dans un JTextField ?

    Il faut passer par ProcessConsumer j’imagine, mais quel process est en paramètre ?

    Sinon, est-il possible de régler l’encodage ?

    La sortie pour un ping par exemple est :

    Envoi d’une requˆte ‘ping’ sur http://www.l.google.com [74.125.43.105] avec 32 octets de donn‚esÿ:
    R‚ponse de 74.125.43.105ÿ: octets=32 temps=65 ms TTL=49
    R‚ponse de 74.125.43.105ÿ: octets=32 temps=66 ms TTL=49
    R‚ponse de 74.125.43.105ÿ: octets=32 temps=65 ms TTL=49
    R‚ponse de 74.125.43.105ÿ: octets=32 temps=66 ms TTL=49

    Statistiques Ping pour 74.125.43.105:
    Paquetsÿ: envoy‚s = 4, re‡us = 4, perdus = 0 (perte 0%),
    Dur‚e approximative des boucles en millisecondes :
    Minimum = 65ms, Maximum = 66ms, Moyenne = 65ms

    Les caractères accentués sont perdus.

    Merci

  8. Paniez

    Re,

    Je tiens à préciser que j’aimerais éviter « l’astuce » du type :

    xterm -e "java -jar progB.jar"

    A l’heure actuelle, la solution la plus « viable » est que je génère en dynamique un launcher (launcher.bat pour windobe et launcher.sh pour linux) avec la commande :

    java -jar progB.jar (qui a l'avantage de libérer les ressources, du moins sous linux)

    qui me donne un assez bon résultat, mais ne me satisfait pas pleinement, d’où ma précédente question.

  9. Paniez

    Bonjour,

    Tout d’abord, bravo pour cet article des plus intéressant.

    J’ai toutefois une question :
    J’aimerais lancer un programme externe (un autre jar), mais que l’appelant se finisse, une fois que l’autre jar est lancé (et ainsi libérer les ressources, car jusqu’à présent, le premier reste dans les processus, ce qui est logique).

    Existe-t-il une solution ?

  10. adiguba Auteur de l’article

    Les commandes interactive comme VI ou TOP doivent être exécuté dans un vrai terminal, car elles utilisent un mode spécial permettant beaucoup plus de chose qu’un simple flux de données.

    C’est donc tout à fait normal que cela ne fonctionne pas…

    Si tu veux les lancer depuis ton programme, il faut passer par un vrai terminal, comme par exemple xterm :

    xterm -e vi

    a++

  11. Avatar de chtibosschtiboss

    Bonjours,

    J’essayer d’executer des commandes interactive linux comme vi ou top
    Mais j’ai toujours le même problème, avec ou sans ta lib.
    avec top j’ai ce retour:
    « top: failed tty get »

    et si j’essayai d’executer vi il s’execute bizarement , la gestion du clavier ne fonctionne pas et il ya des warning au lancement:
    Vim: Warning: Output is not to a terminal
    Vim: Warning: Input is not from a terminal

    Si tu as une idée?
    apparemment il faudrait faire un truc avec tty …

  12. kotobuki

    Eh bien si!! C’est parfait.
    Je me posais justement la question de l’utilisation de .output(PrintStream) et de .error(…), ayant bien compris que la solution était là. Je ne suis pas encore assez coutumier de Java visiblement, puisque je n’avais pas pensé à écrire ça comme ça.

    Merci beaucoup pour cette réponse claire, rapide, et efficace!!

  13. adiguba Auteur de l’article

    Ceci devrait faire l’affaire :

    &nbsp;<br />
    &nbsp;&nbsp;StringBuilder out = new StringBuilder();&nbsp;<br />
    &nbsp;&nbsp;StringBuilder err = new StringBuilder();&nbsp;<br />
    &nbsp;<br />
    &nbsp;&nbsp;Future process = sh.command("curl ...")&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;.output(out).error(err)&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;.consumeInBackground();&nbsp;<br />

    Non ?

    a++

  14. kotobuki

    Bonjour.
    Merci pour cette api qui facilite beaucoup de choses. J’utilise justement un programme qui passe par le terminal habituellement dans un projet. Il s’agit de curl. J’ai besoin de lancer 2 instances de curl en parallèle pour récupérer des flux audio et vidéo depuis un serveur. J’y arrive très bien avec cette API en passant par consumeInBackground(), qui retourne des Future donc.
    Justement, j’ai aussi besoin de récupérer la sortie des erreurs de curl pour que mon programme sache s’il y a eu un problème ou non. Curieusement, je n’ai pas réussi. Les messages s’affichent bien dans la console (sous Eclipse), mais lorsque je tente de mettre le résultat dans un String, je n’ai rien! Pourriez-vous m’indiquer une manière de faire dans ce genre de cas ?

    Merci d’avance, et bravo pour cet outil très utile.

  15. Avatar de dkiefferdkieffer

    En effet je suis sur Linux ( ubuntu et fedora7) , et je suis heureux de te dire encore qu’en fait ton API fonctionne très bien. Et pour le problème du bakslash c’est nous qui nous sommes trompés. Il faut en mettre deux. C’est compréhensible, car si il n’y en a qu’un, il sera interprété au niveau de Java et pas du shell. :-).
    Exemple pour traiter: bon|boulot|adiguba, il faudra taper: « bon\|boulot\|adiguba »

    Et cela fonctionne très bien. Bien sûr, mettre entre cote reste le moyen privilégié.

  16. adiguba Auteur de l’article

    @dkieffer : il faudrait que je jette un coup d’oeil à cela.

    Je suppose que tu tournes sous Linux/Unix car la protection par slash ne fonctionne pas sur la console DOS… mais je n’ai pas de machine où je peux tester cela pour le moment.

    a++

  17. adiguba Auteur de l’article

    @tomca : Désolé de répondre si tard…

    En fait tu as plusieurs solution comme implémenté ton propre Appendable, mais le plus simple est surement d’utiliser les PipedReader/PipedWriter :

    &nbsp;<br />
    &nbsp;&nbsp;String command = ...&nbsp;<br />
    &nbsp;<br />
    &nbsp;&nbsp;Shell sh = new Shell();&nbsp;<br />
    &nbsp;&nbsp;<br />
    &nbsp;&nbsp;PipedReader reader = new PipedReader();&nbsp;<br />
    &nbsp;&nbsp;<br />
    &nbsp;&nbsp;// On prépare la commande&nbsp;<br />
    &nbsp;&nbsp;sh.command(command)&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;// En lui associant un PipedWriter sur sa sortie&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;.output(new PipedWriter(reader))&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;// Et en l'exécutant dans une tâche de fond :&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;.consumeInBackground();&nbsp;<br />
    &nbsp;&nbsp;<br />
    &nbsp;&nbsp;// Puis on lit les données depuis le PipeReader :&nbsp;<br />
    &nbsp;&nbsp;BufferedReader br = new BufferedReader(reader);&nbsp;<br />
    &nbsp;&nbsp;try {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;String line;&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;while ( (line=br.readLine()) != null ) {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(" ==&gt; " + line);&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<br />
    &nbsp;&nbsp;} finally {&nbsp;<br />
    &nbsp;&nbsp;&nbsp;&nbsp;br.close();&nbsp;<br />
    &nbsp;&nbsp;}&nbsp;<br />

  18. Avatar de dkiefferdkieffer

    Resalut!
    Je te confirme que l’on est entrain d’utiliser ton API dans notre labo de bioInfo. En effet, pour gérer nos programmes, cela fonctionne très bien. On a déjà relevé un petit bug. :-) En fait plutôt une fonctionnalité manquante.
    Dans les paramètres à donner aux programmes on a souvent des « | » qui ne sont pas des pipes. Si on utilise ton API en les entourant d’apostrophe, pas de problème, mais si on le « backslash », ( ce qui fonctionne dans une console ), cela ne fonctionne pas via ton API. Je pense qu’il y aura le même problème si il y a un ‘>’ ou un ‘

  19. Avatar de tomcatomca

    Bravo pour cette API, c’est la seule qui reponds à mes besoins…
    En effet, je veux pouvoir lancer une appli en jar sur un serveur à partir d’un autre appli java…et je souhaite récupérer le flux de sortie du programme appellé.
    Je lance donc tout en shell via ssh :
    ssh root@dimsrvli -n « cd /var/www/appli/ && java -jar Insee.jar »
    Ca fonctionne très bien avec ta classe Shell…
    Seulement je souhaiterez récupérer ma sortie dans une boucle pour pouvoir intégrer les retours à mon API Logging…
    En gros, réussir à brancher la sortie out et la sortie err sur un Reader, que je pourrais lire via readline().
    Je me doute qu’il faut utiliser les flux pour cela, mais après de nombreux essais infructueux…je me suis dis que tu pourrais peut être m’aider…
    Merci d’avance!

  20. autoreverse

    rebonjour,
    dans un 1er temps, merci pour ta réponse !
    J’utilisais ta solution en partie … je m’explique :

    je faisais pour la cmd1 un consumeInBackground avec Futur
    puis pour la cmd2 un comsumeAsString avec un String
    donc la cmd2 était bloquée par la cmd1

    Donc conclusion : tout est maintenant en consumeInBackground
    a+

  21. adiguba Auteur de l’article

    Salut,

    1. Il suffit d’utiliser consumeInBackground() pour que les traitements soient exécutées dans une tâche de fond, par exemple :

    Shell shell = new Shell();&nbsp;<br />
    Future cmd1 = shell.system("cmd1").consumeInBackground();&nbsp;<br />
    Future cmd2 = shell.system("cmd2").consumeInBackground();

    2. Il n’y a pas vraiment de processus Shell, mais un processus par commande exécutée. Si tu l’as lancé en tâche de fond tu peux utiliser la méthode cancel(true) de l’objet Future pour arrêter le process…

    a++

  22. autoreverse

    bonjour,
    j’ai 2 questions sur ton api :
    1er : comment faire pour paralléliser l’execution de command line (j’ai vu l’ executor service et le partir Future inBackground mais je « donne ma langue au chat » …)
    N.B : j’appel une appli graphique avec un shell1 et un processconsumer1 spécfique puis une appli en ligne de commande avec son propre shell2 et son processconsumer2 mais celle-ci attend la fin de l’appli graphique ??)
    2eme : comment faire pour « killer » un shell dont on aurait un identifiant (et lequel ?)

    Merci d’avance pour tes réponses !! :)
    et ton api est vraiment sympa !!

  23. adiguba Auteur de l’article

    dkieffer >> Merci !

    J’ai continué à ajouter quelques petites modifications (comme la détection du charset de la console Dos sous Windows, ou l’utilisation du shell utilisateur), et il faudrait que je fasse une doc un peu plus complète, mais dès que je peux je poste tout cela ici ;)

    a++

  24. Avatar de dkiefferdkieffer

    Je m’étais également pencher sur ce problème. J’ai également fait une API ( ProgRunner: http://www-bio3d-igbmc.u-strasbg.fr/~dkieffer/Librairie/librairie.html#ProgRunner )

    Mais avec des gestions d’évenements pour savoir où en été les processus.

    Bref, aujourd’hui, nous passons à UIMA (
    http://incubator.apache.org/uima/ )
    Pour la gestion des programmes externes, et du coup ton API me semble plus légère et plus conviviale que la mienne ( bravo! ).

    Donc, on va regarder ça au labo, et si ça tourne mieux que mon API, on te tient au courant si on corrige des bugs ou rajoute des développements autour! Peut être le début d’une collaboration..

  25. adiguba Auteur de l’article

    Pour les erreurs que tu obtiens avec mon API, cela doit surement venir du fait qu’elle nécessite Java 5.0 au minimum.
    Je vais peut-être faire une version compatible 1.4 voir moins mais cela aura un impact sur les fonctionnalités…

    Quand à ton problème, il vaux mieux utiliser le forum IO car le blog n’est pas vraiment fait pour cela (et le code n’est pas très lisible).

    a++

  26. almensour

    Bonjour
    Je vous salue tout d’abord pour le merveilleux travail,car je suis aussi dans la cadre de travail d’un TP qui consiste à faire des appels des processus extereurs entre autre commande Shell.
    Donc votre bloc me serait d’une grande utilité mais cependant des erreurs se sont declarées dans par exemple la classe ProcessConsumer à la premiere compilation .
    j’ai developé un premier programme qui me fera ses instruction sans utiliser votre blog mais des erreurs se declare aussi mais voici mon premier programme avec seulement l clase Runtime et Proess,si vous pouvez m’aider:
    Le programme devrait permettre d’une maniaire infinie des instructions(commnde en Shell bien « /bin/sh », »-c », »suivi-commande-voulu ») en entrées:J travaille sous Linux(SuSe version 10)…..:

    import java.io.*;
    import java.lang.Process;
    import java.lang.System;
    import java.lang.Runtime;
    import java.lang.Thread;
    class threadE1 extends Thread{
    public String Line2 ,Line3 = null;
    Runtime runtime;
    public Process process;
    threadE1(String Line1){
    Line3=Line1;
    try {
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(new String[] {Line3});
    process.waitFor();
    BufferedReader Lecteur = new BufferedReader(new InputStreamReader(process.getInputStream()));
    BufferedReader Erreur = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    BufferedWriter Ecriture = new BufferedWriter(new OutputStreamReader(process.getOutputStream()));

    }
    catch(IOException ioe){
    while((Line2 = Erreur.readLine()) != null){
    Line2=Erreur.readLine();
    System.out.println(Line2);}
    ioe.printStackTrace();process.destroy();
    }
    catch(InterruptedException e){
    System.out.println(« Application interrompu par le System »);
    e.printStackTrace();
    process.destroy();
    }
    finally {}
    }
    public void run(){
    System.out.println(« La commande est lancée et L’execution de la commande se fait attendre »);

    System.out.println(« La valeur de sortie de l’executione est: »+process.exitValue());
    Lecteur.close();
    Ecriture.close();
    Erreur.close();
    threade1.stop();
    }
    }
    public class Partie1{
    public String valeur = null;
    BufferedReader Entre;
    threadE1 threade1;
    public static void main(String args[]){
    System.out.println(« Le programmme attend les commandes Systeme ?Executer »);
    System.out.println(« Prennez soin de mettre des Double cotes et des sur une seule ligne :Merci »);
    try{
    while(true){
    Entre = new BufferedReader(new ImputStreamReader(System.in));
    valeur=Entre.readLine();
    if(valeur!=null){
    threade1 = new threadE1(valeur);
    threade1.start();}
    }
    }
    catch(IOException ioe) {
    System.out.println(« Erreur d’Entrée Sortie s’est Produite »);
    ioe.printStackTrace();
    }
    finally{
    }
    }
    }

    Et un peu d’exemple pourra par exemple sur l’utilisation de votre blog est voici:import shell.ProcessConsumer;
    import shell.Shell;
    import java.io.*;
    import java.lang.Object;
    public class Utilisation{

    public static void main(String[] args){
    String resultat;
    shell sh=new Shell();
    sh.shell();//au lieu de sh.command(« la commnde »);
    ProcessConsumer ps= new ProcessConsumer(sh);
    resultat1=ps.input( new FileInputStream(System.in));// »in.txt »));//par default le clavier
    resultat2=ps.output( new FileOutputStream(« out.txt »)); //par defaut Ecran
    resultat3=pserror( new FileOutputStream(« err.txt »)); //par defaut Ecran
    ps.consume();
    System.out.println(« La version du shell est : »+sh.toString());
    System.out.println(« La situation de passage des variable d’Environnement : »+sh.isSystemEnvInherited());
    System.outprintln(« Le chartSet par default est : » +sh.getCharset());
    System.out.println(« Le repertoire de Lancemant de la commande est:: »+sh.getDirectory());
    System.out.println( » resultat de sortie à afficher: »+resultat2);
    (essayer apres de boucler sur un programme qui boucle en esxecutant des commandes shell comme dans le premier programme)
    }

    }

    APRES Compilation ses erreurs au sein meme de votre packetages sont generée donc je peux meme pas continuer:
    /*./shell/ProcessConsumer.java:484: illegal start of expression
    Future inputTask = null;
    ^
    ./shell/ProcessConsumer.java:484: not a statement
    Future inputTask = null;
    ^
    ./shell/ProcessConsumer.java:485: illegal start of expression
    Future errorTask = null;
    ^
    ./shell/ProcessConsumer.java:485: not a statement
    Future errorTask = null;
    ^
    ./shell/ProcessConsumer.java:553: expected
    public Future consumeInBackground() {

    Si tou allais bien j’avais voulu lui adapter la même phiolosophie bouclante d’introduction de commande à partir du clavier.

    Merci de vouloir bien m’aider à resoudre le problème.

  27. adiguba Auteur de l’article

    @ZedroS >> Le blog est surtout une autre mode de publication, plus direct où on ne rentre pas vraiment dans les détails…

    Je pense que cela aurait été un peu léger pour un article…

    Enfin, un article demande bien plus de boulot, et je n’ai pas forcément autant de temps à y consacrer…

    Mais si tu veux je garde une liste des billets les « plus intéressants » : http://adiguba.developpez.com/#best_of_blog

    a++

  28. joseph_p

    Salut adiGuba

    Article très intéressant, dommage que ce soit un blog :$ J’dis ça parce que je trouve que les blogs sont bien moins accessibles que les articles (ils ne sont pas, notamment et surtout, référencés dans la partie Java, du moins à ma connaissance).

    Enfin, j’dis ça, j’dis rien ;)

    ++
    ZedroS

Laisser un commentaire