J'essaie d'exécuter une commande externe à partir du code Java, mais j'ai remarqué une différence entre Runtime.getRuntime().exec(...)
et new Process(...).start()
.
Lorsque vous utilisez Runtime
:
Process p = Runtime.getRuntime().exec(installation_path +
uninstall_path +
uninstall_command +
uninstall_arguments);
p.waitFor();
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
revienne.
Que dois-je faire pour résoudre le problème avec ProcessBuilder
?
Les différentes surcharges de Runtime.getRuntime().exec(...)
prennent soit un tableau de chaînes, soit une chaîne unique. Les surcharges de chaîne unique de exec()
vont tokeniser la chaîne en un tableau d'arguments, avant de transmettre le tableau de chaînes à l'une des surcharges exec()
prenant un tableau de chaînes. Les constructeurs ProcessBuilder
, en revanche, prennent uniquement un tableau de chaînes varargs ou un 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 regroupés dans une chaîne transmise au système d'exploitation à exécuter.
Ainsi, par exemple, sous Windows,
Runtime.getRuntime().exec("C:\DoStuff.exe -arg1 -arg2");
va courir un DoStuff.exe
programme avec les deux arguments donnés. Dans ce cas, la ligne de commande est numérotée et reconstituée. cependant,
ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");
échouera, sauf s’il s’agit d’un programme dont le nom est DoStuff.exe -arg1 -arg2
dans 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);
Regardez comment Runtime.getRuntime().exec()
passe la commande String au ProcessBuilder
. Il utilise un tokenizer et décompose la commande en jetons individuels, puis appelle exec(String[] cmdarray, ......)
, qui construit un ProcessBuilder
.
Si vous construisez le ProcessBuilder
avec un tableau de chaînes au lieu d'une seule, vous obtiendrez le même résultat.
Le constructeur ProcessBuilder
prend un String...
vararg, le fait de passer toute la commande sous la forme d'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 méthode Runtime.exec(String)
) prend une seule chaîne de commande qu'elle scinde en une commande et une séquence d'arguments.
Le constructeur ProcessBuilder
prend un tableau de chaînes (varargs). La première chaîne est le nom de la commande et les autres sont les arguments.
Par conséquent, vous demandez à ProcessBuilder d'exécuter une "commande" dont le nom comporte des espaces et d'autres éléments indésirables. Bien entendu, 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 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