Разница между ProcessBuilder и Runtime.exec()
Я пытаюсь выполнить внешнюю команду из кода Java, но я заметил разницу между Runtime.getRuntime().exec(...)
и new ProcessBuilder(...).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
?
Ответы
Ответ 1
Различные перегрузки 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);
Ответ 2
Посмотрите, как Runtime.getRuntime().exec()
передает команду String в ProcessBuilder
. Он использует токенизатор и взрывает команду в отдельные токены, затем вызывает exec(String[] cmdarray, ......)
, который строит a ProcessBuilder
.
Если вы построите ProcessBuilder
с массивом строк вместо одного, вы получите тот же результат.
Конструктор ProcessBuilder
принимает String...
vararg, поэтому передача всей команды в виде отдельной строки имеет тот же эффект, что и вызов этой команды в кавычках в терминале:
shell$ "command with args"
Ответ 3
Да, есть разница.
Итак, что вы говорите, что ProcessBuilder должен выполнить, это выполнить "команду", чье имя содержит пробелы и другие нежелательные файлы. Конечно, операционная система не может найти команду с этим именем, и выполнение команды не выполняется.
Ответ 4
Нет разницы между 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 за комментарий