ProcessBuilder和Runtime.exec()之间的区别


96

我正在尝试从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

Answers:


99

各种重载Runtime.getRuntime().exec(...)采用字符串数组或单个字符串。exec()在将字符串数组传递到采用字符串数组的exec()重载之一之前,的单字符串重载将把字符串标记为参数数组。的ProcessBuilder构造,在另一方面,只需要一可变参数串或阵列List串,其中假定该阵列或列表中的每个字符串为一个单独的参数。不管哪种方式,然后将获得的参数合并为一个字符串,然后将其传递给OS以执行。

因此,例如在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);

它仍然不起作用:List <String> params = java.util.Arrays.asList(installation_path + uninstall_path + uninstall_command,uninstall_arguments); 流程qq = new ProcessBuilder(params).start();
加仑

7
我不认为此字符串连接有任何意义:“ installation_path + uninstall_path + uninstall_command”。
Angel O'Sphere 2011年

8
调用Runtime.getRuntime()。EXEC(...)不调用,除非一个壳明确命令中指定。对于最近的“ Shellshock”错误问题,这是一件好事。这个答案是误导性的,因为它指出将运行cmd.exe或等效文件(即,Unix上的/ bin / bash),但事实并非如此。相反,令牌化是在Java环境中完成的。
Stefan Paul Noack

@ noah1989:感谢您的反馈。我已经更新了答案,希望(希望)弄清楚事情,尤其是删除对shell或的任何提及cmd.exe
卢克·伍德沃德

exec的解析器与参数化版本也不一样,这花了我几天的时间才弄清楚……
Drew Delano

18

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

如果您ProcessBuilder使用字符串数组而不是单个字符串构造,则会得到相同的结果。

所述ProcessBuilder构造函数采用一个String...可变参数,以便使整个命令作为单一的字符串具有如图在终端引号调用该命令相同的效果:

shell$ "command with args"

14

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的评论


2
但是Q不会调用该方法。它(间接)调用public Process exec(String command, String[] envp, File dir)- StringString[]-这呼叫StringTokenizer期权和看跌期权的标记在数组中,然后通过(间接)ProcessBuilder,这是一个差:从7年前的三个答案正确地指出。
dave_thompson_085 '18

问题有多老都没有关系。但是我试图解决答案。
尤金·洛帕特金

我无法为ProcessBuilder设置环境。我只能得到环境...
ilke Muhtaroglu

请参阅docs.oracle.com/javase/7/docs/api/java/lang/…以通过环境方法获取环境后设置环境...
ilke Muhtaroglu,

如果仔细看,您会发现默认情况下该环境为null。
尤金·洛帕特金

14

是,有一点不同。

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

  • ProcessBuilder构造采用串的(可变参数)阵列。第一个字符串是命令名称,其余的是参数。(有一个替代构造函数采用字符串列表,但没有一个采用包含命令和参数的单个字符串。)

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


不,没有区别。Runtime.exec(String)是ProcessBuilder的快捷方式。还支持其他构造函数。
marcolopes

2
你不对 阅读源代码! Runtime.exec(cmd)实际上是的快捷方式Runtime.exec(cmd.split("\\s+"))。该ProcessBuilder班没有一个构造函数,是直接等同Runtime.exec(cmd)。这就是我要回答的重点。
斯蒂芬C

1
实际上,如果您像这样实例化ProcessBuilder:,new ProcessBuilder("command arg1 arg2")start()调用将无法实现您的期望。它可能会失败,并且只有在您的命令名称中带有空格的情况下才会成功。这正是OP所要询问的问题!
斯蒂芬C
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.