Diferencia entre ProcessBuilder y Runtime.exec ()

Estoy intentando ejecutar un comando externo desde el código de Java, pero hay una diferencia que he notado entre Runtime.getRuntime().exec(...) y el new Process(...).start() .

Cuando usas Runtime :

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

exitValue es 0 y el comando finaliza bien.

Sin embargo, con ProcessBuilder :

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

el valor de salida es 1001 y el comando termina en el medio, aunque waitFor devuelve.

¿Qué debo hacer para solucionar el problema con ProcessBuilder ?

Las diversas sobrecargas de Runtime.getRuntime().exec(...) toman una matriz de cadenas o una sola cadena. Las sobrecargas de cadena simple de exec() convertirán la cadena en una matriz de argumentos, antes de pasar la matriz de cadenas a una de las sobrecargas de exec() que toma una matriz de cadenas. Los constructores de ProcessBuilder , por otro lado, solo toman una matriz varargs de cadenas o una List de cadenas, donde se supone que cada cadena de la matriz o lista es un argumento individual. De cualquier manera, los argumentos obtenidos se unen en una cadena que se pasa al sistema operativo para su ejecución.

Entonces, por ejemplo, en Windows,

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

ejecutará un progtwig DoStuff.exe con los dos argumentos dados. En este caso, la línea de comandos se convierte en token y se vuelve a juntar. Sin embargo,

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

fallará, a menos que haya un progtwig cuyo nombre es DoStuff.exe -arg1 -arg2 en C:\ . Esto es porque no hay tokenización: se supone que el comando para ejecutar ya se ha tokenizado. En cambio, debes usar

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

o alternativamente

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

Observe cómo Runtime.getRuntime().exec() pasa el comando String al ProcessBuilder . Utiliza un tokenizer y explota el comando en tokens individuales, luego invoca a exec(String[] cmdarray, ......) que construye un ProcessBuilder .

Si construye ProcessBuilder con una matriz de cadenas en lugar de una sola, obtendrá el mismo resultado.

El constructor ProcessBuilder toma un String... vararg, por lo que pasar el comando completo como una sola Cadena tiene el mismo efecto que invocar ese comando entre comillas en una terminal:

 shell$ "command with args" 

Sí, hay una diferencia.

  • El método Runtime.exec(String) toma una sola cadena de comando que divide en un comando y una secuencia de argumentos.

  • El constructor ProcessBuilder toma una matriz de cadenas (varargs). La primera cadena es el nombre del comando y el rest de ellos son los argumentos.

Entonces, lo que le está diciendo a ProcessBuilder es que ejecute un “comando” cuyo nombre contenga espacios y otros elementos no deseados. Por supuesto, el sistema operativo no puede encontrar un comando con ese nombre, y la ejecución del comando falla.

No hay diferencia entre ProcessBuilder.start() y Runtime.exec() porque la implementación de Runtime.exec() es:

 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(); } 

Entonces código:

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

debe ser el mismo que:

 Runtime.exec(command) 

Gracias dave_thompson_085 para comentarios