Diferencia entre ProcessBuilder y Runtime.exec ()

96

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

Al usar Runtime:

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

exitValue es 0 y el comando finaliza correctamente.

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 waitForregresa.

¿Qué debo hacer para solucionar el problema ProcessBuilder?

galón
fuente

Respuestas:

99

Las diversas sobrecargas de Runtime.getRuntime().exec(...)toman una matriz de cadenas o una sola cadena. Las sobrecargas de una sola cadena de exec()tokenizarán la cadena en una matriz de argumentos, antes de pasar la matriz de cadenas a una de las exec()sobrecargas que toma una matriz de cadenas. Los ProcessBuilderconstructores, por otro lado, solo toman una matriz varargs de cadenas o una Listde cadenas, donde se supone que cada cadena en 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 DoStuff.exeprograma con los dos argumentos dados. En este caso, la línea de comandos se tokeniza y se vuelve a armar. Sin embargo,

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

fallará, a menos que haya un programa cuyo nombre esté DoStuff.exe -arg1 -arg2en C:\. Esto se debe a que no hay tokenización: se supone que el comando a ejecutar ya ha sido tokenizado. En su lugar, debes usar

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

o alternativamente

List<String> params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2");
ProcessBuilder b = new ProcessBuilder(params);
Luke Woodward
fuente
todavía no funciona: List <String> params = java.util.Arrays.asList (ruta_instalación + ruta_desinstalación + comando_desinstalación, argumentos_desinstalaciones); Proceso qq = nuevo ProcessBuilder (params) .start ();
gal
7
No puedo creer que esta concatenación de cadenas tenga sentido: "ruta_instalación + ruta_desinstalación + comando_desinstalación".
Angel O'Sphere
8
Runtime.getRuntime (). Exec (...) NO invoca un shell a menos que el comando lo especifique explícitamente. Eso es bueno con respecto al reciente problema de error "Shellshock". Esta respuesta es engañosa, porque establece que se ejecutará cmd.exe o equivalente (es decir, / bin / bash en unix), lo que no parece ser el caso. En cambio, la tokenización se realiza dentro del entorno Java.
Stefan Paul Noack
@ noah1989: gracias por los comentarios. Actualicé mi respuesta para (con suerte) aclarar las cosas y, en particular, eliminar cualquier mención de shells o cmd.exe.
Luke Woodward
el analizador de Exec no funciona exactamente lo mismo que una la versión con parámetros bien, lo que me dio unos días para averiguar ...
dibujó Delano
18

Mira cómo Runtime.getRuntime().exec()pasa el comando String al ProcessBuilder. Utiliza un tokenizador y descompone el comando en tokens individuales, luego invoca exec(String[] cmdarray, ......)cuál construye un ProcessBuilder.

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

El ProcessBuilderconstructor 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"
Costi Ciudatu
fuente
14

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 codifique:

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

debe ser el mismo que:

Runtime.exec(command)

Gracias dave_thompson_085 por comentar

Eugene Lopatkin
fuente
2
Pero la Q no llama a ese método. (Indirectamente) llama public Process exec(String command, String[] envp, File dir)- StringNO String[]- que llama StringTokenizery coloca los tokens en una matriz que luego se pasa (indirectamente) a ProcessBuilder, lo que ES una diferencia como se indica correctamente en las tres respuestas de hace 7 años.
dave_thompson_085
No importa la antigüedad de la pregunta. Pero trato de arreglar la respuesta.
Eugene Lopatkin
No puedo configurar el entorno para ProcessBuilder. Solo puedo obtener el medio ambiente ...
ilke Muhtaroglu
consulte docs.oracle.com/javase/7/docs/api/java/lang/… para configurar el entorno después de obtenerlos a través del método de entorno ...
ilke Muhtaroglu
Si observa con más atención, podría ver que el entorno por defecto es nulo.
Eugene Lopatkin
14

Sí, hay una diferencia.

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

  • El ProcessBuilderconstructor toma una matriz (varargs) de cadenas. La primera cadena es el nombre del comando y el resto son los argumentos. (Hay un constructor alternativo que toma una lista de cadenas, pero ninguno que toma una sola cadena que consta del comando y los argumentos).

Entonces, lo que le está diciendo a ProcessBuilder que haga es ejecutar un "comando" cuyo nombre tiene espacios y otra basura en él. Por supuesto, el sistema operativo no puede encontrar un comando con ese nombre y la ejecución del comando falla.

Stephen C
fuente
No, no hay diferencia. Runtime.exec (String) es un atajo para ProcessBuilder. Hay otros constructores compatibles.
marcolopes
2
Estás equivocado. ¡Lea el código fuente! Runtime.exec(cmd)es efectivamente un atajo para Runtime.exec(cmd.split("\\s+")). La ProcessBuilderclase no tiene un constructor que sea un equivalente directo de Runtime.exec(cmd). Este es el punto que estoy expresando en mi respuesta.
Stephen C
1
De hecho, si crea una instancia de ProcessBuilder como este:, new ProcessBuilder("command arg1 arg2")la start()llamada no hará lo que espera. Probablemente fallará y solo tendrá éxito si tiene un comando con espacios en su nombre. ¡Este es precisamente el problema por el que se pregunta el OP!
Stephen C