Разница между ProcessBuilder и Runtime.exec () - PullRequest
89 голосов
/ 28 июля 2011

Я пытаюсь выполнить внешнюю команду из кода Java, но я заметил разницу между Runtime.getRuntime().exec(...) и new Process(...).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?

Ответы [ 4 ]

92 голосов
/ 28 июля 2011

Различные перегрузки Runtime.getRuntime().exec(...) принимают либо массив строк, либо одну строку.Одностроковые перегрузки exec() преобразуют строку в массив аргументов перед передачей строкового массива в одну из перегрузок exec(), которая принимает строковый массив.С другой стороны, конструкторы ProcessBuilder принимают только массив строк varargs или 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);
17 голосов
/ 28 июля 2011

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

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

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

shell$ "command with args"
13 голосов
/ 28 июля 2011

Да, есть разница.

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

  • Конструктор ProcessBuilder принимает (varargs) массив строк. Первая строка - это имя команды, а остальные - аргументы.

То, что вы говорите ProcessBuilder, - это выполнение «команды», в имени которой есть пробелы и другой мусор. Конечно, операционная система не может найти команду с таким именем, и выполнение команды завершается неудачей.

11 голосов
/ 11 января 2018

Нет разницы между 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 за комментарий

...