Perbedaan antara ProcessBuilder dan Runtime.exec ()

96

Saya mencoba untuk menjalankan perintah eksternal dari kode java, tetapi ada perbedaan yang saya perhatikan antara Runtime.getRuntime().exec(...)dan new ProcessBuilder(...).start().

Saat menggunakan Runtime:

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

exitValue adalah 0 dan perintah dihentikan dengan ok.

Namun, dengan ProcessBuilder:

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

nilai keluar adalah 1001 dan perintah berakhir di tengah, meskipun waitForkembali.

Apa yang harus saya lakukan untuk mengatasi masalah tersebut ProcessBuilder?

gal
sumber

Jawaban:

99

Berbagai kelebihan Runtime.getRuntime().exec(...)mengambil baik array string atau string tunggal. Overload string tunggal dari exec()akan mengubah string menjadi array argumen, sebelum meneruskan array string ke salah satu exec()kelebihan beban yang mengambil array string. The ProcessBuilderkonstruktor, di sisi lain, hanya mengambil varargs array dari string atau Liststring, di mana setiap string dalam array atau daftar diasumsikan argumen individu. Apa pun caranya, argumen yang diperoleh kemudian digabungkan menjadi string yang diteruskan ke OS untuk dieksekusi.

Jadi, misalnya, di Windows,

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

akan menjalankan DoStuff.exeprogram dengan dua argumen yang diberikan. Dalam kasus ini, baris perintah diberi tokenisasi dan disatukan kembali. Namun,

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

akan gagal, kecuali kebetulan ada program yang namanya ada DoStuff.exe -arg1 -arg2di C:\. Ini karena tidak ada tokenisasi: perintah yang akan dijalankan diasumsikan sudah di-tokenisasi. Sebaliknya, Anda harus menggunakan

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

atau sebagai alternatif

List<String> params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2");
ProcessBuilder b = new ProcessBuilder(params);
Luke Woodward
sumber
masih tidak berfungsi: List <String> params = java.util.Arrays.asList (installation_path + uninstall_path + uninstall_command, uninstall_arguments); Proses qq = baru ProcessBuilder (params) .start ();
gal
7
Saya tidak percaya bahwa rangkaian string ini masuk akal: "installation_path + uninstall_path + uninstall_command".
Angel O'Sphere
8
Runtime.getRuntime (). Exec (...) TIDAK memanggil shell kecuali yang ditentukan secara eksplisit oleh perintah. Itu adalah hal yang baik tentang masalah bug "Shellshock" baru-baru ini. Jawaban ini menyesatkan, karena menyatakan bahwa cmd.exe atau yang setara (yaitu / bin / bash di unix) akan dijalankan, yang tampaknya tidak demikian. Sebagai gantinya, tokenisasi dilakukan di dalam lingkungan Java.
Stefan Paul Noack
@ noah1989: terima kasih atas umpan baliknya. Saya telah memperbarui jawaban saya untuk (semoga) mengklarifikasi hal-hal dan khususnya menghapus penyebutan shell atau cmd.exe.
Luke Woodward
parser untuk exec juga tidak berfungsi sama dengan versi berparameter, yang membutuhkan waktu beberapa hari untuk saya ketahui ...
Drew Delano
18

Lihatlah bagaimana Runtime.getRuntime().exec()melewatkan perintah String ke ProcessBuilder. Ia menggunakan tokenizer dan meledakkan perintah menjadi token individu, lalu memanggil exec(String[] cmdarray, ......)yang menyusun aProcessBuilder .

Jika Anda membuat file ProcessBuilder dengan larik string, bukan satu, Anda akan mendapatkan hasil yang sama.

The ProcessBuilderkonstruktor mengambil String...vararg, sehingga melewati seluruh perintah sebagai String tunggal memiliki efek yang sama seperti memanggil perintah yang dalam tanda kutip di terminal:

shell$ "command with args"
Costi Ciudatu
sumber
14

Tidak ada perbedaan antara ProcessBuilder.start()dan Runtime.exec()karena penerapannya Runtime.exec()adalah:

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

Jadi kode:

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

harus sama dengan:

Runtime.exec(command)

Terima kasih dave_thompson_085 atas komentarnya

Eugene Lopatkin
sumber
2
Tetapi Q tidak menyebut metode itu. Ini (secara tidak langsung) memanggil public Process exec(String command, String[] envp, File dir)- StringNOT String[]- yang memanggil StringTokenizerdan menempatkan token dalam array yang kemudian diteruskan (secara tidak langsung) ke ProcessBuilder, yang IS perbedaan seperti yang dinyatakan dengan benar oleh tiga jawaban dari 7 tahun yang lalu.
dave_thompson_085
Tidak peduli berapa umur pertanyaannya. Tetapi saya mencoba untuk memperbaiki jawaban.
Eugene Lopatkin
Saya tidak dapat mengatur lingkungan untuk ProcessBuilder. Saya hanya bisa mendapatkan lingkungan ...
ilke Muhtaroglu
lihat docs.oracle.com/javase/7/docs/api/java/lang/… untuk mengatur lingkungan setelah mendapatkannya melalui metode lingkungan ...
ilke Muhtaroglu
Jika Anda melihat lebih teliti, Anda dapat melihat bahwa lingkungan secara default adalah null.
Eugene Lopatkin
14

Ya ada bedanya.

  • The Runtime.exec(String)Metode mengambil string perintah tunggal yang terbagi menjadi perintah dan urutan argumen.

  • The ProcessBuilderkonstruktor mengambil (varargs) array dari string. String pertama adalah nama perintah dan sisanya adalah argumen. (Ada konstruktor alternatif yang mengambil daftar string, tetapi tidak ada yang mengambil satu string yang terdiri dari perintah dan argumen.)

Jadi apa yang Anda perintahkan untuk dilakukan ProcessBuilder adalah menjalankan "perintah" yang namanya memiliki spasi dan sampah lain di dalamnya. Tentu saja, sistem operasi tidak dapat menemukan perintah dengan nama tersebut, dan eksekusi perintah gagal.

Stephen C
sumber
Tidak, tidak ada perbedaan. Runtime.exec (String) adalah jalan pintas untuk ProcessBuilder. Ada konstruktor lain yang didukung.
marcolopes
2
Anda salah. Baca kode sumbernya! Runtime.exec(cmd)secara efektif merupakan jalan pintas untuk Runtime.exec(cmd.split("\\s+")). The ProcessBuilderkelas tidak memiliki konstruktor yang setara langsung ke Runtime.exec(cmd). Inilah poin yang saya buat dalam jawaban saya.
Stephen C
1
Bahkan, jika Anda instantiate ProcessBuilder seperti ini: new ProcessBuilder("command arg1 arg2"), para start()panggilan tidak akan melakukan apa yang Anda harapkan. Ini mungkin akan gagal, dan hanya akan berhasil jika Anda memiliki perintah dengan spasi dalam namanya. Inilah masalah yang ditanyakan OP!
Stephen C