Різниця між ProcessBuilder та Runtime.exec ()


96

Я намагаюся виконати зовнішню команду з коду Java, але я помітив різницю між Runtime.getRuntime().exec(...)і new ProcessBuilder(...).start().

При використанні Runtime:

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

exitValue дорівнює 0, і команда завершується нормально.

Однак із ProcessBuilder:

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

значення виходу - 1001, і команда закінчується посередині, хоча waitForповертається.

Що робити, щоб вирішити проблему ProcessBuilder?

Відповіді:


99

Різні перевантаження Runtime.getRuntime().exec(...)приймають або масив рядків, або один рядок. exec()Однорядкові перевантаження перетворюють рядок на масив аргументів, перш ніж передавати масив рядків на одне із exec()перевантажень, яке приймає масив рядків. З ProcessBuilderіншого боку, конструктори беруть лише масив varargs з рядків або a Listз рядків, де кожен рядок у масиві чи списку вважається індивідуальним аргументом. У будь-якому випадку отримані аргументи потім об’єднуються в рядок, який передається ОС для виконання.

Так, наприклад, у Windows,

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

запустить DoStuff.exeпрограму з двома наведеними аргументами. У цьому випадку командний рядок отримує маркер і складає його назад. Однак,

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

не вдасться, якщо тільки не буде програми, назва якої DoStuff.exe -arg1 -arg2в C:\. Це тому, що немає токенізації: передбачається, що команда для запуску вже була токенізована. Натомість вам слід використовувати

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

або альтернативно

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

він все ще не працює: List <String> params = java.util.Arrays.asList (path_instalation + uninstall_path + uninstall_command, uninstall_arguments); Процес qq = новий ProcessBuilder (параметри) .start ();
gal

7
Я не можу повірити, що ця конкатанація рядків має якийсь сенс: "шлях_установки + шлях видалення + команда видалення_команди".
Angel O'Sphere

8
Runtime.getRuntime (). Exec (...) НЕ викликає оболонку, якщо це явно не вказано командою. Це добре щодо нещодавньої проблеми з помилками "Shellshock". Ця відповідь вводить в оману, оскільки в ній зазначено, що cmd.exe або еквівалент (тобто / bin / bash на unix) буде запущений, що, схоже, не так. Натомість токенізація проводиться всередині середовища Java.
Stefan Paul Noack,

@ noah1989: дякую за відгук. Я оновив свою відповідь, щоб (сподіваюся) пояснити речі і, зокрема, видалити будь-які згадки про снаряди або cmd.exe.
Luke Woodward,

аналізатор для exec теж не працює так само, як параметризована версія, і мені знадобилося кілька днів, щоб зрозуміти ...
Дрю Делано,

18

Подивіться, як Runtime.getRuntime().exec()передає команду String команді ProcessBuilder. Він використовує маркер і розбиває команду на окремі маркери, а потім викликає, exec(String[] cmdarray, ......)що створює a ProcessBuilder.

Якщо ви побудуєте ProcessBuilderмасив з масивом рядків замість одного, ви отримаєте той самий результат.

ProcessBuilderКонструктор приймає String...vararg, тому передаючи всю команду у вигляді одного рядка має той же ефект, викликаючи цю команду в лапках в терміналі:

shell$ "command with args"

14

Немає різниці між ProcessBuilder.start()і Runtime.exec()тому, що реалізація 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();
}

Отже, код:

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

має бути таким самим, як:

Runtime.exec(command)

Дякую dave_thompson_085 за коментар


2
Але Q не називає цей метод. Він (непрямо) викликає public Process exec(String command, String[] envp, File dir)- StringНЕ String[]- який викликає StringTokenizerта поміщає маркери в масив, який потім передається (опосередковано) ProcessBuilder, що Є різницею, як правильно вказано у трьох відповідях 7 років тому.
dave_thompson_085

Не має значення, скільки років питанню. Але я намагаюся виправити відповідь.
Євген Лопаткін,

Я не можу встановити середовище для ProcessBuilder. Я можу отримати лише навколишнє середовище ...
ilke Muhtaroglu

див. docs.oracle.com/javase/7/docs/api/java/lang/…, щоб встановити середовище після отримання їх методом середовища ...
ilke Muhtaroglu

Якщо ви уважніше подивитесь, ви зможете побачити, що середовище за замовчуванням є нульовим.
Євген Лопаткін

14

Так, є різниця.

  • Runtime.exec(String)Метод займає один рядок команди , що він розщеплює в команду і послідовності аргументів.

  • ProcessBuilderКонструктор приймає () з перемінним числом аргументів масив рядків. Перший рядок - це ім'я команди, а решта - аргументи. (Існує альтернативний конструктор, який бере список рядків, але жоден, який бере єдиний рядок, що складається з команди та аргументів.)

Отже, те, що ви говорите ProcessBuilder, - це виконати "команду", в імені якої є пробіли та інший мотлох. Звичайно, операційна система не може знайти команду з таким ім’ям, і виконання команди не вдається.


Ні, різниці немає. Runtime.exec (String) - це ярлик для ProcessBuilder. Є й інші підтримувані конструктори.
marcolopes

2
Ви неправильні. Прочитайте вихідний код! Runtime.exec(cmd)є фактично ярликом для Runtime.exec(cmd.split("\\s+")). ProcessBuilderКлас не має конструктора , який є прямим еквівалентом Runtime.exec(cmd). На цьому я підкреслюю свою відповідь.
Стівен С

1
Справді, якщо ви екземпляр ProcessBuilder так: new ProcessBuilder("command arg1 arg2"), то start()виклик не буде робити те , що ви очікуєте. Ймовірно, це не вдасться, і це вдасться лише в тому випадку, якщо у вас є команда з пробілами в її назві. Це саме проблема, про яку запитує ОП!
Стівен С
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.