Differenza tra ProcessBuilder e Runtime.exec ()


96

Sto cercando di eseguire un comando esterno da codice java, ma ho notato una differenza tra Runtime.getRuntime().exec(...)e new ProcessBuilder(...).start().

Quando si utilizza Runtime:

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

exitValue è 0 e il comando è terminato ok.

Tuttavia, con ProcessBuilder:

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

il valore di uscita è 1001 e il comando termina a metà, sebbene waitForritorni.

Cosa devo fare per risolvere il problema ProcessBuilder?

Risposte:


99

I vari overload di Runtime.getRuntime().exec(...)accettano un array di stringhe o una singola stringa. Gli overload a stringa singola di exec()tokenizzeranno la stringa in un array di argomenti, prima di passare l'array di stringhe a uno degli exec()overload che accetta un array di stringhe. I ProcessBuildercostruttori, d'altra parte, accettano solo un array di stringhe varargs o un array di Liststringhe, dove si presume che ogni stringa nell'array o nell'elenco sia un singolo argomento. In ogni caso, gli argomenti ottenuti vengono quindi uniti in una stringa che viene passata al sistema operativo per l'esecuzione.

Quindi, ad esempio, su Windows,

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

eseguirà un DoStuff.exeprogramma con i due argomenti dati. In questo caso, la riga di comando viene tokenizzata e rimessa insieme. Però,

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

fallirà, a meno che non ci sia un programma il cui nome è DoStuff.exe -arg1 -arg2in C:\. Questo perché non c'è tokenizzazione: si presume che il comando da eseguire sia già stato tokenizzato. Invece, dovresti usare

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

o in alternativa

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

ancora non funziona: List <String> params = java.util.Arrays.asList (percorso_installazione + percorso_installazione + comando_ disinstallazione, argomenti_installazione); Process qq = new ProcessBuilder (params) .start ();
gal

7
Non riesco a credere che questa concatanazione di stringhe abbia senso: "percorso_installazione + percorso_installazione + comando_ disinstallazione".
Angel O'Sphere

8
Runtime.getRuntime (). Exec (...) NON richiama una shell a meno che non sia esplicitamente specificato dal comando. Questa è una buona cosa riguardo al recente problema del bug "Shellshock". Questa risposta è fuorviante, perché afferma che cmd.exe o equivalente (cioè / bin / bash su unix) verrebbe eseguito, il che non sembra essere il caso. Invece la tokenizzazione viene eseguita all'interno dell'ambiente Java.
Stefan Paul Noack

@ noah1989: grazie per il feedback. Ho aggiornato la mia risposta per (si spera) chiarire le cose e in particolare rimuovere qualsiasi menzione di shell o cmd.exe.
Luke Woodward

il parser per exec non funziona esattamente come una versione parametrizzata, il che mi ha portato alcuni giorni per capire ...
Drew Delano

18

Guarda come Runtime.getRuntime().exec()passa il comando String al file ProcessBuilder. Usa un tokenizer ed esplode il comando in singoli token, quindi invoca exec(String[] cmdarray, ......)che costruisce un file ProcessBuilder.

Se costruisci il ProcessBuildercon un array di stringhe invece di uno singolo, otterrai lo stesso risultato.

Il ProcessBuildercostruttore accetta un String...vararg, quindi passare l'intero comando come una singola stringa ha lo stesso effetto dell'invocazione di quel comando tra virgolette in un terminale:

shell$ "command with args"

14

Non ci sono differenze tra ProcessBuilder.start()e Runtime.exec()perché l'implementazione di Runtime.exec()è:

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

Quindi codice:

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

dovrebbe essere lo stesso di:

Runtime.exec(command)

Grazie dave_thompson_085 per il commento


2
Ma il Q non chiama quel metodo. Chiama (indirettamente) public Process exec(String command, String[] envp, File dir)- StringNOT String[]- che chiama StringTokenizere mette i token in un array che viene poi passato (indirettamente) a ProcessBuilder, il che È una differenza come correttamente affermato dalle tre risposte di 7 anni fa.
dave_thompson_085

Non importa quanti anni ha la domanda. Ma provo a risolvere la risposta.
Eugene Lopatkin

Non riesco a impostare l'ambiente per ProcessBuilder. Posso solo ottenere l'ambiente ...
ilke Muhtaroglu

vedere docs.oracle.com/javase/7/docs/api/java/lang/… per impostare l'ambiente dopo averli ottenuti tramite il metodo ambientale ...
ilke Muhtaroglu

Se guardi più attentamente potresti vedere che l'ambiente di default è nullo.
Eugene Lopatkin

14

Sì, c'è differenza.

  • Il Runtime.exec(String)metodo accetta una singola stringa di comando che divide in un comando e una sequenza di argomenti.

  • Il ProcessBuildercostruttore accetta un array di stringhe (varargs). La prima stringa è il nome del comando e le altre sono gli argomenti. (Esiste un costruttore alternativo che accetta un elenco di stringhe, ma nessuno che accetta una singola stringa composta dal comando e dagli argomenti.)

Quindi quello che stai dicendo a ProcessBuilder di fare è eseguire un "comando" il cui nome contiene spazi e altra spazzatura. Ovviamente, il sistema operativo non riesce a trovare un comando con quel nome e l'esecuzione del comando fallisce.


No, non c'è differenza. Runtime.exec (String) è una scorciatoia per ProcessBuilder. Sono supportati altri costruttori.
marcolopes

2
Non sei corretto. Leggi il codice sorgente! Runtime.exec(cmd)è effettivamente una scorciatoia per Runtime.exec(cmd.split("\\s+")). La ProcessBuilderclasse non ha un costruttore che sia un equivalente diretto a Runtime.exec(cmd). Questo è il punto che sto sottolineando nella mia risposta.
Stephen C

1
Infatti, se installi un ProcessBuilder in questo modo:, new ProcessBuilder("command arg1 arg2")la start()chiamata non farà quello che ti aspetti. Probabilmente fallirà e avrà successo solo se hai un comando con spazi nel nome. Questo è precisamente il problema che si pone l'OP!
Stephen C
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.