Разница между ProcessBuilder и Runtime.exec()

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

Когда используешь Runtime:

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

значение выхода равно 0, и команда завершена нормально.

Однако с ProcessBuilder:

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

значение выхода равно 1001, и команда заканчивается посередине, хотя waitFor возвращается.

Что я должен сделать, чтобы решить проблему с ProcessBuilder?

4 ответа

Решение

Различные перегрузки 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);

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

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

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

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

shell$ "command with args"

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

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

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

Итак, что вы говорите ProcessBuilder, так это выполнение "команды", в имени которой есть пробелы и другой мусор. Конечно, операционная система не может найти команду с таким именем, и ее выполнение не выполняется.

Другие вопросы по тегам