Using Java's exec command when you don't know if there's be spaces
Asked Answered
W

6

5

I'm fighting the spaces bug in Java's Runtime exec method. Here's what's unique about this problem: the command I'm trying to execute is an incoming string and may or may not have spaces and is not necessarily in any specific format. Either way, I need to execute it. If there are no spaces, I'm good; if there are spaces, I'm not so good.

How do I account for both circumstances?

Bonus info at no extra charge: One of the big issues appears to be that I'm trying to call an executable in c:\program files\blablabla... and exec appears to split on the space after 'c:\program'. I'm sure other issues would come up for the parameters, too.

Here's a more specific example of the kinds of strings I might get. That should clear up some of the confusion:

  • c:\someApp\someapp.exe
  • c:\someApp\someapp.exe -someParam=foo
  • c:\program files\someapp\someapp.exe
  • c:\program files\someapp\someapp.exe -someParam=bar

The first one works fine because it has no spaces. The second is even okay because it splits on the space and uses the first as a command and second as a parameter. The third and fourth examples split on the first space, use 'C:\program' and the command, 'files...' and (in the case of the fourth string) '-someParam=bar' as parameters.

Winthrop answered 1/11, 2010 at 16:28 Comment(2)
Are you able to do some pre-processing of the incoming string? If so, you can use the Runtime.exec(String[] cmdarray) method which will handle spaces for you, assuming you're able to break the incoming string up by arguments.Claudication
Garbage In, Garbage Out; you can't parse a nonsense string. Make sure the sender escape the spaces you want to retain, or put the strings that runs together in quotes; only by doing so, your problem turns from impossible to rather difficult.Chandelier
W
4

Okay, I got something working by doing something like this. Please tell me if there's a problem with this approach:


try{
    String[] command = {"cmd", "/c", getMySuperAwesomeString()};
    Runtime.getRuntime().exec(command);
}catch(IOExecption ioe){
    System.err.println("I'm borken");
}

On a related note, should I use ProcessBuilder instead?

Winthrop answered 1/11, 2010 at 17:31 Comment(3)
This will probably work, depending on the process you're starting. If it works it's probably more flexible than my answer (and with less coding). You're just offloading the hard part of figuring out the arguments to the cmd shell :) You shouldn't need ProcessBuilder unless you're wanting to start a whole bunch of similar processes, as you'd run into the same tokenizing problems.Claudication
Yeah, either way you're making assumptions, right? Thanks for your input!Winthrop
Yep this is what I had to do in a microservice, was a real head scratcher but this method seems far more robust.Nilsanilsen
C
4

I'll actually make this an answer instead of a comment: (from the J2SE 1.5, Runtime.exec(String[]))

Assuming you can preprocess, use a String array to alleviate the problems with spaces in commands, the following should work:

String[] args = {"C:\Program Files\app\app.exe","C:\Data Files\data1.dat"};
Runtime.exec(args);

From there it depends on being able to figure out what is a space between two commands and what is a space in a path.

EDIT

This will work if the spaces appear in the executable's path, but won't help you on spaces in the arguments.

String input = "c:\\program files\\someapp\\someapp.exe -someParam=bar";
int firstSplit = input.indexOf(".exe") + 4; //account for length of ".exe"
String command = input.substring(0,firstSplit);
String args = input.substring(firstSplit).trim(); //trim off extraneous whitespace
String[] argarray = args.split(" ");
String[] cmdargs = new String[argarray.length + 1];
cmdargs[0] = command;
for (int i = 0; i < argarray.length; i++) {
    cmdargs[i+1] = argarray[i];
}
Runtime.exec(cmdargs);

Note that this is still fairly fragile (and only works for exe's, not bat's or whatever else). If you need to account for spaces in the arguments, you'll need to do more processing to either the args string or the argarray. The proper solution is to get your users (or the input process) to properly differentiate all the arguments.

Claudication answered 1/11, 2010 at 16:55 Comment(3)
I'd like to do something like this, but I have no way of reliably knowing where to break the command string.Winthrop
That makes things rather unfortunate, if not impossible. You could kind of guess at it, though: (from your edit) if the spaces only occur in the path to the executable, preprocess to take everything up to ".exe" as one command, and then do a String.split(" ") on the remaining options. If the options themselves or the arguments can contain spaces, it's going to get painful. I'll edit my answer to show an example of that.Claudication
for (int i = 0; i < argarray.length; i++) { cmdargs[i+1] = argarray[i]; } can be written as System.arraycopy(argarray, 0, cmdargs, 1, argarray.length);Admeasure
W
4

Okay, I got something working by doing something like this. Please tell me if there's a problem with this approach:


try{
    String[] command = {"cmd", "/c", getMySuperAwesomeString()};
    Runtime.getRuntime().exec(command);
}catch(IOExecption ioe){
    System.err.println("I'm borken");
}

On a related note, should I use ProcessBuilder instead?

Winthrop answered 1/11, 2010 at 17:31 Comment(3)
This will probably work, depending on the process you're starting. If it works it's probably more flexible than my answer (and with less coding). You're just offloading the hard part of figuring out the arguments to the cmd shell :) You shouldn't need ProcessBuilder unless you're wanting to start a whole bunch of similar processes, as you'd run into the same tokenizing problems.Claudication
Yeah, either way you're making assumptions, right? Thanks for your input!Winthrop
Yep this is what I had to do in a microservice, was a real head scratcher but this method seems far more robust.Nilsanilsen
E
1

Example to run a JAR file with spaces in the file location:

String qoutation = "\""
String path = "C:\\JAR File\\File.jar"
Runtime.getRuntime().exec("java -jar " + quotation + path + quotation);

This runs the command: java -jar "C:\Jar File\File.jar"

When the backslash \ appears in a string it is used as an escape, effectively making the program skip it. This allows us to use the quotation mark " in the string, not to start/close the string.

Ely answered 21/1, 2013 at 10:20 Comment(0)
S
0

Maybe I'm thinking too simply, but could this work?

Runtime.exec(commandstring.split(" "));

(I'm assuming that you want the command to be executed as if executed in a shell or so.)

Sluggard answered 1/11, 2010 at 16:34 Comment(1)
I think he's thinking of something like command C:\Documents and Settings\user\datafile.dat, which you wouldn't want to interpret as ["command","C:\Documents","and","Settings\user\datafile.dat"]Claudication
D
0

I'll assume you can preprocess the incoming string. In that case, you can see if you have an input with spaces with:

if("string with possible spaces".contains(" ")) {
    System.out.println("yay");
}

Note that this will work only if there's an actual space character in your input. If you want to be more thorough, assuming tab characters or newlines are possible inputs, you can use a regex:

String s = ".+\\s.+";
Pattern p = Pattern.compile(s);

Matcher m = p.matcher("string with possible spaces or    tabs");
if(m.matches()) {
    System.out.println("yay");
}

Edit: You can use the code I provided to detect if there are spaces in the input. If there aren't, you don't have to do anything. If you have spaces, the situation is a bit more complex. Reading other people's answers and your revised question, I can only assume that the input that has spaces has to be quoted, so spacing won't mess up the standard command execution. In there are spaces, you should:

  1. check if there are any quotes in your input
  2. if there are any, escape them (change " to \")
  3. surround the input with quotes (some input becomes "some input")

Ultimately, you want input like some "funky" input to become "some \"funky\" input".

Note: beware of single quotes ('). Deal with them appropriately.

Dover answered 1/11, 2010 at 16:50 Comment(1)
I could split it, but what would I do with the individual pieces? I just wouldn't know what I have a hold of after I split them.Winthrop
C
0

I am assuming this is the bug you are fighting:

https://bugs.java.com/bugdatabase/view_bug?bug_id=4506936

https://bugs.java.com/bugdatabase/view_bug;jsessionid=8582245ba20fa4da33ac0967e858?bug_id=4491217

They say that the workaround would be:

Use a JRE that is not located in a path that contains spaces.

Use public Process exec(String[] cmdarray, String[] envp) throws IOException

and put the command and parameters in a separate array. This way it wouldn't try to tockenize it and use space as a separator.

Calder answered 1/11, 2010 at 16:55 Comment(3)
Our JRE is not located in a path that contains spaces.Winthrop
On the other bug they say that you need to put all the arguments in the separate slot: To summarize, the only safe way is to put each argument into a separate slot in arguments array. So, use download.oracle.com/javase/1.4.2/docs/api/java/lang/… . I will correct the original post.Calder
The problem is that I don't know what's an argument and what's not.Winthrop

© 2022 - 2024 — McMap. All rights reserved.