116

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?

0

4 Answers 4

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);
Sign up to request clarification or add additional context in comments.

6 Comments

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();
I can not believe that this string concatanation makes any sense: "installation_path+uninstall_path+uninstall_command".
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.
@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.
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...
|
25

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

6 Comments

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.
It does not matter how old the question is. But i try to fix answer.
I can not set the environment for ProcessBuilder. I can only get the environment...
see docs.oracle.com/javase/7/docs/api/java/lang/… to set environment after getting them via environment method...
If you look more carefully you could see that environment by default is null.
|
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"

Comments

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.

3 Comments

No, there is not a difference. Runtime.exec(String) is a shortcut for ProcessBuilder. There are other constructors supported.
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.
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!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.