I have bash script that takes in a couple of arguments tests then and runs a command as shown below
call script => /bin/bash myscript arg1 arg2 arg3 arg4
comm called => command -a arg1 -b arg2 -c arg3 -d arg4
if a parameter is empty then that option is not called.
call script => /bin/bash myscript arg1 arg2 '' arg4
comm called => command -a arg1 -b arg2 -d arg4
I am able to achieve this by using following line in the script to test the arguments
if test "${arg3:-t}" != "t" ; then
This script works like a charm when called from prompt. even if I replace ” with “” for an empty argument it works fine.
This starts failing when I call this script from java using exec.
Process p = Runtime.getRuntime().exec("/bin/bash myscript arg1 arg2 '' arg4");
expected command => command -a arg1 -b arg2 -d arg4 (as in above example)
actual command => command -a arg1 -b arg2 -c '' -d arg4
I am not able to understand why this would be happening. Where is the problem? In the shell script or in the way it command is executed from java?
How can this be fixed?
The basic problem is that java doesn’t do anything at all fancy to parse the string you give it and break it nicely into arguments.
The short answer is to use the
String []version ofRuntime.exec:If you have other places where the argument parsing is more convoluted than that, you can pass the parsing of the string off to bash, and do:
If you’re converting something over that’s doing a huge number of complicated redirects like
2>&1or setting up a whole pipeline, you might need thisbash -ctrick.EDIT:
To understand what’s going on here, you have to realize that when user-space code tells the kernel “load this executable with these arguments and start a process based on that” (*), what’s passed on to the kernel is an executable, an array of strings for arguments (the 0th/first element of this argument array is the name of the executable, except when doing something weird), and an array of strings for the environment.
What bash does when it sees this line:
is think “Okay,
/bin/bashisn’t a builtin, so I’m executing something. Let’s put together the argument array for the subprocess using my string parsing algorithms, which know about quotes and whatnot”. Bash then determines that the arguments to pass the kernel for the new process are:/bin/bashmyscriptarg1arg2(empty string)
arg4Now bash has pretty complicated string processing algorithms that it applies here – it accepts two different kinds of quotes, it’ll do expansion of
$VARwhen it happens outside strings or inside double quotes, it’ll replace subcommands in backquotes with the output, etc.Java doesn’t do anything so sophisticated when you call the single string version of
exec. It just creates a newStringTokenizerand uses that to break up the string you give it into arguments. That class doesn’t know anything about quotes; it splits that string up into:/bin/bashmyscriptarg1arg2''(a string with two characters, both of which are the single quote)arg4Java then calls the
String[]version ofexec. (Well, one of them)Notes for people picking nits with this:
(*) Yes, I’m deliberately eliding the difference between the system calls
forkandexecve. Pretend they’re one call for the moment.