Recherche…


Remarques

Notez que l'API recommande qu'à partir de la version 1.5, la méthode préférée pour créer un processus utilise ProcessBuilder.start() .

Une autre remarque importante est que la valeur de sortie produite par waitFor dépend du programme / script exécuté. Par exemple, les codes de sortie produits par calc.exe sont différents de notepad.exe .

Exemple simple (version Java <1.5)

Cet exemple appellera la calculatrice Windows. Il est important de noter que le code de sortie variera en fonction du programme / script appelé.

package process.example;

import java.io.IOException;

public class App {

    public static void main(String[] args) {
        try {
            // Executes windows calculator
            Process p = Runtime.getRuntime().exec("calc.exe");

            // Wait for process until it terminates
            int exitCode = p.waitFor();

            System.out.println(exitCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Utiliser la classe ProcessBuilder

La classe ProcessBuilder facilite l'envoi d'une commande via la ligne de commande. Tout ce qu'il faut, c'est une liste de chaînes qui composent les commandes à saisir. Vous appelez simplement la méthode start () sur votre instance ProcessBuilder pour exécuter la commande.

Si vous avez un programme appelé Add.exe qui prend deux arguments et les ajoute, le code ressemblera à ceci:

List<String> cmds = new ArrayList<>();
cmds.add("Add.exe"); //the name of the application to be run
cmds.add("1"); //the first argument
cmds.add("5"); //the second argument

ProcessBuilder pb = new ProcessBuilder(cmds);

//Set the working directory of the ProcessBuilder so it can find the .exe
//Alternatively you can just pass in the absolute file path of the .exe
File myWorkingDirectory = new File(yourFilePathNameGoesHere);
pb.workingDirectory(myWorkingDirectory);

try {
    Process p = pb.start(); 
} catch (IOException e) {
    e.printStackTrace();
}

Quelques points à garder en tête:

  • Le tableau de commandes doit tous être un tableau String
  • Les commandes doivent être dans l'ordre (dans le tableau) qu'elles seraient si vous appeliez le programme dans la ligne de commande (c.-à-d. Que le nom du fichier
  • Lorsque vous définissez le répertoire de travail, vous devez transmettre un objet File et pas uniquement le nom du fichier en tant que String.

Appels bloquants ou non bloquants

En général, lors d'un appel à la ligne de commande, le programme envoie la commande puis continue son exécution.

Cependant, vous voudrez peut-être attendre que le programme appelé se termine avant de poursuivre votre propre exécution (par exemple, le programme appelé écrira des données dans un fichier et votre programme en aura besoin pour accéder à ces données).

Cela peut facilement être fait en appelant la méthode waitFor() partir de l'instance Process retournée.

Exemple d'utilisation:

//code setting up the commands omitted for brevity...

ProcessBuilder pb = new ProcessBuilder(cmds);

try {
    Process p = pb.start();
    p.waitFor();
} catch (IOException e) {
    e.printStackTrack();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//more lines of code here...

ch.vorburger.exec

Le lancement direct de processus externes à partir de Java en utilisant directement l’API java.lang.ProcessBuilder peut être un peu compliqué. La bibliothèque Apache Commons Exec facilite les choses. La bibliothèque ch.vorburger.exec s'étend sur Commons Exec pour le rendre vraiment pratique:

 ManagedProcess proc = new ManagedProcessBuilder("path-to-your-executable-binary")
     .addArgument("arg1")
     .addArgument("arg2")
     .setWorkingDirectory(new File("/tmp"))
     .setDestroyOnShutdown(true)
     .setConsoleBufferMaxLines(7000)
     .build();

proc.start();
int status = proc.waitForExit();
int status = proc.waitForExitMaxMsOrDestroy(3000);
String output = proc.getConsole();

proc.startAndWaitForConsoleMessageMaxMs("started!", 7000);
// use service offered by external process...
proc.destroy();

Piège: Runtime.exec, Process et ProcessBuilder ne comprennent pas la syntaxe du shell

Les Runtime.exec(String ...) et Runtime.exec(String) vous permettent d'exécuter une commande en tant que processus externe 1 . Dans la première version, vous indiquez le nom de la commande et les arguments de la commande en tant qu'éléments distincts du tableau de chaînes et le runtime Java demande au système d'exécution du système d'exploitation de démarrer la commande externe. La deuxième version est facile à utiliser, mais elle comporte des pièges.

Tout d'abord, voici un exemple d'utilisation de exec(String) utilisé en toute sécurité:

Process p = Runtime.exec("mkdir /tmp/testDir");
p.waitFor();
if (p.exitValue() == 0) {
    System.out.println("created the directory");
}

Espaces dans les chemins

Supposons que nous généralisions l'exemple ci-dessus pour pouvoir créer un répertoire arbitraire:

Process p = Runtime.exec("mkdir " + dirPath);
// ...

Cela fonctionnera généralement, mais cela échouera si dirPath est (par exemple) "/ home / user / My Documents". Le problème est que exec(String) divise la chaîne en une commande et les arguments en recherchant simplement des espaces. La chaîne de commande:

"mkdir /home/user/My Documents"

sera divisé en:

"mkdir", "/home/user/My", "Documents"

et cela provoquera l'échec de la commande "mkdir" car elle attend un argument, pas deux.

Face à cela, certains programmeurs essaient d’ajouter des guillemets autour du chemin. Cela ne fonctionne pas non plus:

"mkdir \"/home/user/My Documents\""

sera divisé en:

"mkdir", "\"/home/user/My", "Documents\""

Les caractères supplémentaires entre guillemets qui ont été ajoutés pour tenter de "citer" les espaces sont traités comme tous les autres caractères non blancs. En effet, tout ce que nous citons ou échappons dans les espaces va échouer.

La manière de gérer ces problèmes particuliers consiste à utiliser la surcharge exec(String ...) .

Process p = Runtime.exec("mkdir", dirPath);
// ...

Cela fonctionnera si dirpath inclut des caractères d' dirpath car cette surcharge de exec ne tente pas de diviser les arguments. Les chaînes sont transmises à l'appel du système d' exec système d'exploitation tel exec .

Redirection, pipelines et autres syntaxes de shell

Supposons que nous voulions rediriger l'entrée ou la sortie d'une commande externe ou exécuter un pipeline. Par exemple:

Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");

ou

Process p = Runtime.exec("find source -name *.java | xargs grep package");

(Le premier exemple répertorie les noms de tous les fichiers Java du système de fichiers et le second affiche les instructions du package 2 dans les fichiers Java de l'arborescence "source".)

Ceux-ci ne vont pas fonctionner comme prévu. Dans le premier cas, la commande "find" sera exécutée avec "2> / dev / null" comme argument de commande. Il ne sera pas interprété comme une redirection. Dans le deuxième exemple, le caractère de tuyau ("|") et les travaux suivants seront donnés à la commande "find".

Le problème est que les méthodes exec et ProcessBuilder ne comprennent aucune syntaxe de shell. Cela inclut les redirections, les pipelines, l'expansion des variables, la mise en mémoire, etc.

Dans quelques cas (par exemple, une simple redirection), vous pouvez facilement obtenir l'effet souhaité en utilisant ProcessBuilder . Cependant, ce n'est pas vrai en général. Une autre approche consiste à exécuter la ligne de commande dans un shell; par exemple:

Process p = Runtime.exec("bash", "-c", 
                         "find / -name *.java -print 2>/dev/null");

ou

Process p = Runtime.exec("bash", "-c", 
                         "find source -name \\*.java | xargs grep package");

Mais notez que dans le deuxième exemple, nous avons dû échapper au caractère générique ("*") car nous voulons que le caractère générique soit interprété par "trouver" plutôt que par le shell.

Les commandes intégrées du shell ne fonctionnent pas

Supposons que les exemples suivants ne fonctionnent pas sur un système avec un shell de type UNIX:

Process p = Runtime.exec("cd", "/tmp");     // Change java app's home directory

ou

Process p = Runtime.exec("export", "NAME=value");  // Export NAME to the java app's environment

Il y a quelques raisons pour lesquelles cela ne fonctionnera pas:

  1. Sur "cd" et "export", les commandes sont des commandes intégrées au shell. Ils n'existent pas en tant qu'exécutables distincts.

  2. Pour que les commandes intégrées à la coquille fassent ce qu’elles sont censées faire (par exemple, modifier le répertoire de travail, mettre à jour l’environnement), elles doivent changer l’emplacement de cet état. Pour une application normale (y compris une application Java), l'état est associé au processus d'application. Ainsi, par exemple, le processus fils qui exécuterait la commande "cd" ne pourrait pas modifier le répertoire de travail de son processus parent "java". De même, un exec processus « d ne peut pas changer le répertoire de travail pour un processus qui suit.

Ce raisonnement s'applique à toutes les commandes intégrées du shell.


1 - Vous pouvez également utiliser ProcessBuilder , mais cela n’est pas pertinent au sens de cet exemple.

2 - C'est un peu dur et prêt ... mais encore une fois, les défauts de cette approche ne sont pas pertinents pour l'exemple.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow