Разница между 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конструкторах, с другой стороны, только взять переменную длину массива строк или а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 (путь_установки + путь_удаления + команда_удаления, аргументы_удаления); Процесс qq = new ProcessBuilder (params) .start ();
gal
7
Я не могу поверить, что это объединение строк имеет какой-либо смысл: «путь_установки + путь_удаления + команда_удаления».
Angel O'Sphere
8
Runtime.getRuntime (). Exec (...) НЕ вызывает оболочку, если это явно не указано командой. Это хорошо в отношении недавней проблемы с ошибкой "Shellshock". Этот ответ вводит в заблуждение, поскольку в нем говорится, что будет запущен cmd.exe или его эквивалент (например, / bin / bash в unix), что, похоже, не так. Вместо этого токенизация выполняется в среде Java.
Стефан Пол Ноак
@ noah1989: спасибо за отзыв. Я обновил свой ответ, чтобы (надеюсь) прояснить ситуацию и, в частности, удалить любое упоминание об оболочках или cmd.exe.
Люк Вудворд
синтаксический анализатор для exec работает не так же, как параметризованная версия, и мне потребовалось несколько дней, чтобы разобраться ...
Дрю Делано
18

Посмотрите, как Runtime.getRuntime().exec()передает команду String объекту ProcessBuilder. Он использует токенизатор и разбивает команду на отдельные токены, а затем вызывает, exec(String[] cmdarray, ......)который создаетProcessBuilder .

Если вы построите ProcessBuilder с массивом строк вместо одной, вы получите тот же результат.

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

shell$ "command with args"
Costi Ciudatu
источник
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/…, чтобы установить среду после получения их с помощью метода среды ...
Илке Мухтароглу
Если вы посмотрите более внимательно, вы увидите, что среда по умолчанию пуста.
Евгений Лопаткин
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()вызов не будет делать то , что вы ожидаете. Вероятно, это не удастся и будет успешным только в том случае, если у вас есть команда с пробелами в ее имени. Это как раз та проблема, о которой спрашивает ОП!
Стивен С.