105

我正在尝试从 java 代码执行外部命令,但我注意到Runtime.getRuntime().exec(...)和之间存在差异new ProcessBuilder(...).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

4 回答 4

109

的各种重载Runtime.getRuntime().exec(...)采用字符串数组或单个字符串。的单字符串重载exec()会将字符串标记为参数数组,然后将字符串数组传递给采用字符串数组的exec()重载之一。ProcessBuilder另一方面,构造函数只接受一个可变参数数组或一个字符串List,其中数组或列表中的每个字符串都被假定为一个单独的参数。无论哪种方式,然后将获得的参数连接成一个字符串,然后传递给操作系统执行。

因此,例如,在 Windows 上,

Runtime.getRuntime().exec("C:\DoStuff.exe -arg1 -arg2");

将运行DoStuff.exe带有两个给定参数的程序。在这种情况下,命令行被标记化并重新组合在一起。然而,

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");

将失败,除非碰巧有一个名称DoStuff.exe -arg1 -arg2C:\. 这是因为没有标记化:假定要运行的命令已经标记化。相反,您应该使用

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);
于 2011-07-28T09:18:29.810 回答
20

看看如何Runtime.getRuntime().exec()将 String 命令传递给ProcessBuilder. 它使用分词器并将命令分解为单个令牌,然后调用exec(String[] cmdarray, ......)构造一个ProcessBuilder.

如果你ProcessBuilder用一个字符串数组而不是一个字符串来构造,你会得到相同的结果。

构造ProcessBuilder函数采用String...可变参数,因此将整个命令作为单个字符串传递与在终端中用引号调用该命令具有相同的效果:

shell$ "command with args"
于 2011-07-28T08:34:00.430 回答
20

和之间没有区别ProcessBuilder.start()Runtime.exec()因为Runtime.exec()is 的实现:

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发表评论

于 2018-01-11T14:30:47.727 回答
17

是,有一点不同。

  • Runtime.exec(String)方法采用单个命令字符串,将其拆分为命令和一系列参数。

  • 构造ProcessBuilder函数接受一个 (varargs) 字符串数组。第一个字符串是命令名称,其余的是参数。(有一个替代构造函数接受字符串列表,但没有一个接受由命令和参数组成的单个字符串。)

因此,您告诉 ProcessBuilder 要做的是执行一个名称中包含空格和其他垃圾的“命令”。当然,操作系统找不到具有该名称的命令,因此命令执行失败。

于 2011-07-28T08:34:20.710 回答