Pregunta Diferencia entre ProcessBuilder y Runtime.exec ()


Intento ejecutar un comando externo desde el código Java, pero hay una diferencia que noté entre Runtime.getRuntime().exec(...) y 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 devoluciones.

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


74
2017-07-28 08:25


origen


Respuestas:


Las diversas sobrecargas de Runtime.getRuntime().exec(...) tomar una serie de cadenas o una sola cadena. Las sobrecargas de cadena simple de exec() pondrá 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 ProcessBuilder los constructores, 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 DoStuff.exe programa 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 programa cuyo nombre sea 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<String> params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2");
ProcessBuilder b = new ProcessBuilder(params);

75
2017-07-28 09:18



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

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

los ProcessBuilder constructor toma una 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"

16
2017-07-28 08:34



Sí, hay una diferencia.

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.


12
2017-07-28 08:34



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<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 para comentarios


2
2018-01-11 14:30