it-swarm.com.ru

Разница между 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?

89
gal

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

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

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

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

Shell$ "command with args"
17
Costi Ciudatu

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

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

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

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

13
Stephen C

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

11
Eugene Lopatkin