Différence entre ProcessBuilder et Runtime.exec ()

J’essaie d’exécuter une commande externe à partir du code Java, mais j’ai remarqué une différence entre Runtime.getRuntime().exec(...) et le new Process(...).start() .

Lors de l’utilisation du Runtime :

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

l’exitValue est à 0 et la commande est terminée 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 que waitFor retourne.

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

Les différentes surcharges de Runtime.getRuntime().exec(...) prennent un tableau de chaînes ou une chaîne unique. Les surcharges de chaîne unique de exec() vont transformer la chaîne en un tableau d’arguments, avant de passer le tableau de chaînes à l’une des surcharges exec() qui prend un tableau de chaînes. Les constructeurs ProcessBuilder , quant à eux, ne prennent qu’un tableau de chaînes varargs ou une List de 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 dans une chaîne qui est transmise au système d’exploitation pour être exécutée.

Donc, par exemple, sous Windows,

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

exécutera un programme DoStuff.exe avec les deux arguments donnés. Dans ce cas, la ligne de commande est marquée et remise en place. cependant,

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

échouera, sauf s’il existe un programme dont le nom est DoStuff.exe -arg1 -arg2 dans C:\ . C’est parce qu’il n’y a pas de tokenisation: la commande à exécuter est supposée avoir déjà été symbolisée. Au lieu de cela, vous devriez utiliser

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

Ou bien

 List params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2"); ProcessBuilder b = new ProcessBuilder(params); 

Regardez comment Runtime.getRuntime().exec() transmet la commande Ssortingng à ProcessBuilder . Il utilise un tokenizer et fait exploser la commande en jetons individuels, puis appelle exec(Ssortingng[] cmdarray, ......) qui construit un ProcessBuilder .

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

Le constructeur ProcessBuilder prend un Ssortingng... vararg, ainsi, passer la commande entière en une seule chaîne a le même effet que d’appeler cette commande entre guillemets dans un terminal:

 shell$ "command with args" 

Oui, il y a une différence.

  • La Runtime.exec(Ssortingng) prend une seule chaîne de commande Runtime.exec(Ssortingng) en une commande et une séquence d’arguments.

  • Le constructeur ProcessBuilder prend un tableau (varargs) de chaînes. La première chaîne est le nom de la commande et les autres sont les arguments.

Donc, ce que vous dites à ProcessBuilder, c’est d’exécuter une “commande” dont le nom contient des espaces et d’autres éléments indésirables. Bien sûr, le système d’exploitation ne peut pas trouver une commande portant ce nom et l’exécution de la commande échoue.

Il n’y a pas de différence entre ProcessBuilder.start() et Runtime.exec() car l’implémentation de Runtime.exec() est:

 public Process exec(Ssortingng command) throws IOException { return exec(command, null, null); } public Process exec(Ssortingng command, Ssortingng[] envp, File dir) throws IOException { if (command.length() == 0) throw new IllegalArgumentException("Empty command"); SsortingngTokenizer st = new SsortingngTokenizer(command); Ssortingng[] cmdarray = new Ssortingng[st.countTokens()]; for (int i = 0; st.hasMoreTokens(); i++) cmdarray[i] = st.nextToken(); return exec(cmdarray, envp, dir); } public Process exec(Ssortingng[] cmdarray, Ssortingng[] envp, File dir) throws IOException { return new ProcessBuilder(cmdarray) .environment(envp) .directory(dir) .start(); } 

Donc le code:

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

devrait être le même que:

 Runtime.exec(command) 

Merci dave_thompson_085 pour le commentaire