Difference between ProcessBuilder and Runtime.exec()
Asked Answered
B

4

114

I'm trying to execute an external command from java code, but there's a difference I've noticed between Runtime.getRuntime().exec(...) and new ProcessBuilder(...).start().

When using Runtime:

Process p = Runtime.getRuntime().exec(installation_path + 
                                       uninstall_path + 
                                       uninstall_command + 
                                       uninstall_arguments);
p.waitFor();

the exitValue is 0 and the command is terminated ok.

However, with ProcessBuilder:

Process p = (new ProcessBuilder(installation_path +    
                                 uninstall_path +
                                 uninstall_command,
                                 uninstall_arguments)).start();
p.waitFor();

the exit value is 1001 and the command terminates in the middle, although waitFor returns.

What should I do to fix the problem with ProcessBuilder?

Buchenwald answered 28/7, 2011 at 8:25 Comment(0)
L
113

The various overloads of Runtime.getRuntime().exec(...) take either an array of strings or a single string. The single-string overloads of exec() will tokenise the string into an array of arguments, before passing the string array onto one of the exec() overloads that takes a string array. The ProcessBuilder constructors, on the other hand, only take a varargs array of strings or a List of strings, where each string in the array or list is assumed to be an individual argument. Either way, the arguments obtained are then joined up into a string that is passed to the OS to execute.

So, for example, on Windows,

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

will run a DoStuff.exe program with the two given arguments. In this case, the command-line gets tokenised and put back together. However,

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

will fail, unless there happens to be a program whose name is DoStuff.exe -arg1 -arg2 in C:\. This is because there's no tokenisation: the command to run is assumed to have already been tokenised. Instead, you should use

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

or alternatively

List<String> params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2");
ProcessBuilder b = new ProcessBuilder(params);
Legitimacy answered 28/7, 2011 at 9:18 Comment(6)
it still doesn't work: List<String> params = java.util.Arrays.asList(installation_path+uninstall_path+uninstall_command, uninstall_arguments); Process qq=new ProcessBuilder(params).start();Buchenwald
I can not believe that this string concatanation makes any sense: "installation_path+uninstall_path+uninstall_command".Kiefer
Runtime.getRuntime().exec(...) does NOT invoke a shell unless that is explicitly specified by the command. That is a good thing regarding the recent "Shellshock" bug issue. This answer is misleading, because it states that cmd.exe or equivalent (i.e. /bin/bash on unix) would be run, which does not seem to be the case. Instead tokenization is done inside the Java environment.Kalin
@noah1989: thanks for the feedback. I've updated my answer to (hopefully) clarify things and in particular remove any mention of shells or cmd.exe.Legitimacy
the parser for exec doesn't work quite the same as a the parameterized version either, which took me a few days to figure out...Roxane
@DrewDelano - yea. The "parser" is effectively String.split("\\s+") :-)Meri
V
23

There are no difference between ProcessBuilder.start() and Runtime.exec() because implementation of 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();
}

So code:

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();

should be the same as:

Runtime.exec(command)

Thanks dave_thompson_085 for comment

Vivisectionist answered 11/1, 2018 at 14:30 Comment(6)
But the Q doesn't call that method. It (indirectly) calls public Process exec(String command, String[] envp, File dir) -- String NOT String[] -- which calls StringTokenizer and puts the tokens in an array which is then passed (indirectly) to ProcessBuilder, which IS a difference as correctly stated by the three answers from 7 years ago.Solfeggio
It does not matter how old the question is. But i try to fix answer.Vivisectionist
I can not set the environment for ProcessBuilder. I can only get the environment...Visit
see docs.oracle.com/javase/7/docs/api/java/lang/… to set environment after getting them via environment method...Visit
If you look more carefully you could see that environment by default is null.Vivisectionist
There is three overloaded methods exec.Vivisectionist
P
20

Look at how Runtime.getRuntime().exec() passes the String command to the ProcessBuilder. It uses a tokenizer and explodes the command into individual tokens, then invokes exec(String[] cmdarray, ......) which constructs a ProcessBuilder.

If you construct the ProcessBuilder with an array of strings instead of a single one, you'll get to the same result.

The ProcessBuilder constructor takes a String... vararg, so passing the whole command as a single String has the same effect as invoking that command in quotes in a terminal:

shell$ "command with args"
Perusse answered 28/7, 2011 at 8:34 Comment(0)
M
19

Yes there is a difference.

  • The Runtime.exec(String) method takes a single command string that it splits into a command and a sequence of arguments.

  • The ProcessBuilder constructor takes a (varargs) array of strings. The first string is the command name and the rest of them are the arguments. (There is an alternative constructor that takes a list of strings, but none that takes a single string consisting of the command and arguments.)

So what you are telling ProcessBuilder to do is to execute a "command" whose name has spaces and other junk in it. Of course, the operating system can't find a command with that name, and the command execution fails.

Meri answered 28/7, 2011 at 8:34 Comment(3)
No, there is not a difference. Runtime.exec(String) is a shortcut for ProcessBuilder. There are other constructors supported.Sunbow
You are incorrect. Read the source code! Runtime.exec(cmd) is effectively a shortcut for Runtime.exec(cmd.split("\\s+")). The ProcessBuilder class does not have a constructor that is a direct equivalent to Runtime.exec(cmd). This is the point I am making in my answer.Meri
In fact, if you instantiate a ProcessBuilder like this: new ProcessBuilder("command arg1 arg2"), the start() call will not do what you expect. It will probably fail, and will only succeed if you have a command with spaces in its name. This is precisely the problem that the OP is asking about!Meri

© 2022 - 2024 — McMap. All rights reserved.