
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 :
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...Bref : avec comme simple objectif de vouloir lancer un programme, on se retrouve à devoir gérer des lectures de flux et du multithreading...
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é :
<, >, >>, 2>, 2>> et 2>&1.| et des opérateurs booléens && et ||.%NAME% sous Windows et $NAME sous les systèmes Unix).echo, cd>, etc.).*, ?, \, 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).
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...
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.../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.
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).
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.
http://blog.developpez.com/htsrv/trackback.php?tb_id=3035
Shell shell = new Shell();
Future cmd1 = shell.system("cmd1").consumeInBackground();
Future cmd2 = shell.system("cmd2").consumeInBackground();
String command = ...
Shell sh = new Shell();
PipedReader reader = new PipedReader();
// On prépare la commande
sh.command(command)
// En lui associant un PipedWriter sur sa sortie
.output(new PipedWriter(reader))
// Et en l'exécutant dans une tâche de fond :
.consumeInBackground();
// Puis on lit les données depuis le PipeReader :
BufferedReader br = new BufferedReader(reader);
try {
String line;
while ( (line=br.readLine()) != null ) {
System.out.println(" ==> " + line);
}
} finally {
br.close();
}
StringBuilder out = new StringBuilder();
StringBuilder err = new StringBuilder();
Future process = sh.command("curl ...")
.output(out).error(err)
.consumeInBackground();
xterm -e vixterm -e "java -jar progB.jar"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. Shell shell = new Shell();
shell.setCharset("cp850");
shell.command("ping www.google.com").consume();public class AppendableJTextComponent implements Appendable {
private final JTextComponent component;
public AppendableJTextComponent(JTextComponent comp) {
this.component = comp;
}
@Override
public Appendable append(char c) throws IOException {
appendStringOnEDT(Character.toString(c));
return this;
}
@Override
public Appendable append(CharSequence csq, int start, int end)
throws IOException {
appendStringOnEDT(csq.subSequence(start, end).toString());
return this;
}
@Override
public Appendable append(CharSequence csq) throws IOException {
appendStringOnEDT(csq.toString());
return this;
}
private void appendStringOnEDT(final String str) {
if (SwingUtilities.isEventDispatchThread()) {
Document doc = component.getDocument();
if (doc != null) {
try {
doc.insertString(doc.getLength(), str, null);
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
}
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
appendStringOnEDT(str);
}
});
}
}
}
final JTextPane textpane = new JTextPane();
JButton button = new JButton("ping");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new SwingWorker() {
@Override
protected Integer doInBackground() throws Exception {
Shell shell = new Shell();
shell.setCharset("cp850");
return shell.command("ping www.google.com").output(new AppendableJTextComponent(textpane)).consume();
}
}.execute();
}
});
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400,400);
frame.setLocationRelativeTo(null);
frame.getContentPane().add(new JScrollPane(textpane));
frame.getContentPane().add(button, BorderLayout.SOUTH);
frame.setVisible(true);
Vous devez être identifié pour poster un commentaire.

Ce blog est mis à disposition sous un contrat Creative Commons BY-NC-SA.
System.gc() a encore frappé : jre 1.5, tomcat 6.0 et multi processeurs
Tutoriels Java SE
Tutoriels Java EE
Sélection du blog
Java SE/EE et Web en général
| Lun | Mar | Mer | Jeu | Ven | Sam | Dim |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
Copyright © 2000-2012 - www.developpez.com