Différence entre ProcessBuilder et Runtime.exec ()

96

J'essaie d'exécuter une commande externe à partir du code java, mais il y a une différence que j'ai remarquée entre Runtime.getRuntime().exec(...)et new ProcessBuilder(...).start().

Lors de l'utilisation Runtime:

Process p = Runtime.getRuntime().exec(installation_path + 
                                       uninstall_path + 
                                       uninstall_command + 
                                       uninstall_arguments);
p.waitFor();

exitValue est 0 et la commande se termine ok.

Cependant, avec ProcessBuilder:

Process p = (new ProcessBuilder(installation_path +    
                                 uninstall_path +
                                 uninstall_command,
                                 uninstall_arguments)).start();
p.waitFor();

la valeur de sortie est 1001 et la commande se termine au milieu, bien qu'elle waitForretourne.

Que dois-je faire pour résoudre le problème ProcessBuilder?

fille
la source

Réponses:

99

Les différentes surcharges de Runtime.getRuntime().exec(...)prennent soit un tableau de chaînes, soit une seule chaîne. Les surcharges sur une seule chaîne de exec()tokeniseront la chaîne en un tableau d'arguments, avant de passer le tableau de chaînes sur l'une des exec()surcharges qui prend un tableau de chaînes. Les ProcessBuilderconstructeurs, en revanche, n'acceptent qu'un tableau varargs de chaînes ou Listde chaînes, où chaque chaîne du tableau ou de la liste est supposée être un argument individuel. Dans tous les cas, les arguments obtenus sont ensuite réunis en une chaîne qui est transmise au système d'exploitation pour s'exécuter.

Ainsi, par exemple, sous Windows,

Runtime.getRuntime().exec("C:\DoStuff.exe -arg1 -arg2");

exécutera un DoStuff.exeprogramme avec les deux arguments donnés. Dans ce cas, la ligne de commande est tokenisée et reconstituée. cependant,

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");

échouera, à moins qu'il y ait un programme dont le nom figure DoStuff.exe -arg1 -arg2dans C:\. En effet, il n'y a pas de tokenisation: la commande à exécuter est supposée avoir déjà été tokenisée. Au lieu de cela, vous devriez utiliser

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe", "-arg1", "-arg2");

Ou bien

List<String> params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2");
ProcessBuilder b = new ProcessBuilder(params);
Luke Woodward
la source
cela ne fonctionne toujours pas: List <String> params = java.util.Arrays.asList (chemin_installation + chemin_installation + commande_install, arguments_sinstallation); Process qq = nouveau ProcessBuilder (paramètres) .start ();
gal
7
Je ne peux pas croire que cette concatanation de chaînes ait un sens: "installation_path + uninstall_path + uninstall_command".
Angel O'Sphere
8
Runtime.getRuntime (). Exec (...) n'invoque PAS un shell sauf si cela est explicitement spécifié par la commande. C'est une bonne chose en ce qui concerne le récent problème de bogue "Shellshock". Cette réponse est trompeuse, car elle indique que cmd.exe ou équivalent (c'est-à-dire / bin / bash sous unix) serait exécuté, ce qui ne semble pas être le cas. Au lieu de cela, la tokenisation est effectuée dans l'environnement Java.
Stefan Paul Noack
@ noah1989: merci pour les commentaires. J'ai mis à jour ma réponse pour (j'espère) clarifier les choses et en particulier supprimer toute mention de shells ou cmd.exe.
Luke Woodward
l'analyseur pour exec ne fonctionne pas tout à fait de la même manière que la version paramétrée non plus, ce qui m'a pris quelques jours pour comprendre ...
Drew Delano
18

Regardez comment Runtime.getRuntime().exec()passe la commande String au ProcessBuilder. Il utilise un tokenizer et décompose la commande en tokens individuels, puis invoque exec(String[] cmdarray, ......)lequel construit unProcessBuilder .

Si vous construisez le ProcessBuilder avec un tableau de chaînes au lieu d'une seule, vous obtiendrez le même résultat.

Le ProcessBuilderconstructeur prend un String...vararg, donc passer la commande entière comme une seule chaîne a le même effet que l'appel de cette commande entre guillemets dans un terminal:

shell$ "command with args"
Costi Ciudatu
la source
14

Il n'y a aucune différence entre ProcessBuilder.start()et Runtime.exec()parce que la mise en œuvre de Runtime.exec()est:

public Process exec(String command) throws IOException {
    return exec(command, null, null);
}

public Process exec(String command, String[] envp, File dir)
    throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");

    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}

public Process exec(String[] cmdarray, String[] envp, File dir)
    throws IOException {
    return new ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
}

Donc code:

List<String> list = new ArrayList<>();
new StringTokenizer(command)
.asIterator()
.forEachRemaining(str -> list.add((String) str));
new ProcessBuilder(String[])list.toArray())
            .environment(envp)
            .directory(dir)
            .start();

devrait être le même que:

Runtime.exec(command)

Merci dave_thompson_085 pour le commentaire

Eugène Lopatkin
la source
2
Mais le Q n'appelle pas cette méthode. Il appelle (indirectement) public Process exec(String command, String[] envp, File dir)- StringNON String[]- qui appelle StringTokenizeret met les jetons dans un tableau qui est ensuite passé (indirectement) à ProcessBuilder, ce qui est une différence comme correctement indiqué par les trois réponses d'il y a 7 ans.
dave_thompson_085
Peu importe l'âge de la question. Mais j'essaye de corriger la réponse.
Eugene Lopatkin
Je ne peux pas définir l'environnement pour ProcessBuilder. Je ne peux obtenir que l'environnement ...
ilke Muhtaroglu
voir docs.oracle.com/javase/7/docs/api/java/lang/… pour définir l'environnement après les avoir obtenus via la méthode d'environnement ...
ilke Muhtaroglu
Si vous regardez plus attentivement, vous pouvez voir que l'environnement par défaut est nul.
Eugene Lopatkin
14

Oui, il y a une différence.

  • La Runtime.exec(String)méthode prend une seule chaîne de commande qu'elle divise en une commande et une séquence d'arguments.

  • Le ProcessBuilderconstructeur prend un tableau (varargs) de chaînes. La première chaîne est le nom de la commande et les autres sont les arguments. (Il existe un constructeur alternatif qui prend une liste de chaînes, mais aucun ne prend une seule chaîne composée de la commande et des arguments.)

Donc, ce que vous dites à ProcessBuilder de faire est d'exécuter une "commande" dont le nom contient des espaces et d'autres fichiers indésirables. Bien sûr, le système d'exploitation ne trouve pas de commande portant ce nom et l'exécution de la commande échoue.

Stephen C
la source
Non, il n'y a pas de différence. Runtime.exec (String) est un raccourci pour ProcessBuilder. Il existe d'autres constructeurs pris en charge.
marcolopes
2
Vous avez tort. Lisez le code source! Runtime.exec(cmd)est en fait un raccourci pour Runtime.exec(cmd.split("\\s+")). La ProcessBuilderclasse n'a pas de constructeur qui est un équivalent direct de Runtime.exec(cmd). C'est ce que je veux dire dans ma réponse.
Stephen C
1
En fait, si vous instanciez un ProcessBuilder comme ceci:, new ProcessBuilder("command arg1 arg2")l' start()appel ne fera pas ce que vous attendez. Il échouera probablement et ne réussira que si vous avez une commande avec des espaces dans son nom. C'est précisément le problème que se pose le PO!
Stephen C