Java ProcessBuilder和Runtime.exec()之间的差异
我试图从java代码执行一个外部命令,但我注意到Java ProcessBuilder和Runtime.exec()之间的差异,java,runtime.exec,processbuilder,Java,Runtime.exec,Processbuilder,我试图从java代码执行一个外部命令,但我注意到Runtime.getRuntime().exec(…)和newProcessBuilder(…).start()之间存在差异 使用运行时时: Process p = Runtime.getRuntime().exec(installation_path + uninstall_path +
Runtime.getRuntime().exec(…)
和newProcessBuilder(…).start()
之间存在差异
使用运行时时
:
Process p = Runtime.getRuntime().exec(installation_path +
uninstall_path +
uninstall_command +
uninstall_arguments);
p.waitFor();
exitValue为0,命令终止正常
但是,对于Process Builder:
Process p = (new ProcessBuilder(installation_path +
uninstall_path +
uninstall_command,
uninstall_arguments)).start();
p.waitFor();
退出值为1001,命令终止于中间,虽然<代码> WaITOO< /COD>返回。
我应该如何解决
ProcessBuilder
的问题?看看Runtime.getRuntime().exec()
如何将字符串命令传递给ProcessBuilder
。它使用标记器并将命令分解为单个标记,然后调用exec(String[]cmdarray,…)
,这将构造一个ProcessBuilder
如果使用字符串数组而不是单个字符串来构造ProcessBuilder
,则会得到相同的结果
ProcessBuilder
构造函数接受一个String…
vararg,因此将整个命令作为单个字符串传递与在终端中以引号调用该命令具有相同的效果:
shell$ "command with args"
是的,有区别
- 获取单个命令字符串,并将其拆分为一个命令和一系列参数
- 获取字符串的(varargs)数组。第一个字符串是命令名,其余的是参数。(有一个替代构造函数接受字符串列表,但没有一个构造函数接受由命令和参数组成的单个字符串。)
因此,您要告诉ProcessBuilder执行的是一个“命令”,其名称中包含空格和其他垃圾。当然,操作系统找不到具有该名称的命令,并且命令执行失败。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");
将失败,除非在C:\
中有一个名为DoStuff.exe-arg1-arg2的程序。这是因为没有标记化:假定要运行的命令已经标记化。相反,您应该使用
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 params=java.util.Arrays.asList(“C:\DoStuff.exe”、“-arg1”、“-arg2”);
ProcessBuilder b=新的ProcessBuilder(参数);
由于运行时.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();
}
So代码:
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();
感谢dave_thompson_085发表评论它仍然不起作用:List params=java.util.Arrays.asList(安装路径+卸载路径+卸载命令,卸载参数);Process qq=新的ProcessBuilder(params).start();我不相信这个字符串组合有任何意义:“installation_path+uninstall_path+uninstall_command”。Runtime.getRuntime().exec(…)不会调用shell,除非命令显式指定。关于最近的“Shellshock”bug问题,这是一件好事。这个答案是有误导性的,因为它声明将运行cmd.exe或等效程序(即unix上的/bin/bash),但事实似乎并非如此。相反,标记化是在Java环境中完成的。@noah1989:感谢您的反馈。我已经更新了我的答案(希望)澄清了一些事情,特别是删除了对Shell或cmd.exe
的任何提及。exec解析器的工作方式与参数化版本也不完全相同,我花了几天时间才弄明白……但Q没有调用该方法。它(间接地)调用publicprocessexec(String命令,String[]envp,File dir)
--String
而不是String[]
--它调用StringTokenizer
,并将令牌放入数组中,然后(间接地)传递给ProcessBuilder
,这与7年前的三个答案所正确表述的不同,这与问题的年代无关。但我试图解决这个问题。我无法为ProcessBuilder设置环境。我只能获取环境…请参阅通过环境方法获取环境后设置环境…如果您仔细查看,您可以看到默认环境为空。不,没有区别。exec(字符串)是ProcessBuilder的快捷方式。支持其他构造函数。您不正确。阅读源代码Runtime.exec(cmd)
实际上是Runtime.exec(cmd.split(\\s+))
的快捷方式。ProcessBuilder
类没有直接等效于Runtime.exec(cmd)
的构造函数。这就是我在回答中的要点。事实上,如果您像这样实例化ProcessBuilder:newProcessBuilder(“命令arg1 arg2”)
,则start()
调用将无法实现您的预期。它可能会失败,并且只有在命令名中包含空格时才会成功。这正是OP所问的问题!
Runtime.exec(command)