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 of Runtime.exec
:
Runtime.getRuntime().exec(
new String[] {"/bin/bash", "myscript", "arg1", "arg2", "", "arg4"});
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:
Runtime.getRuntime().exec(
new String[] {"/bin/bash", "-c", "/bin/bash myscript arg1 arg2 '' arg4"});
If you're converting something over that's doing a huge number of complicated redirects like 2>&1
or setting up a whole pipeline, you might need this bash -c
trick.
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:
/bin/bash myscript arg1 arg2 '' arg4
is think "Okay, /bin/bash
isn'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/bash
myscript
arg1
arg2
(empty string)
arg4
Now bash has pretty complicated string processing algorithms that it applies here - it accepts two different kinds of quotes, it'll do expansion of $VAR
when 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 new StringTokenizer
and 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/bash
myscript
arg1
arg2
''
(a string with two characters, both of which are the single quote)
arg4
Java then calls the String[]
version of exec
. (Well, one of them)
Notes for people picking nits with this:
(*) Yes, I'm deliberately eliding the difference between the system calls fork
and execve
. Pretend they're one call for the moment.
Try this:
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "myscript", "arg1", "arg2", "", "arg4");
Process proc = pb.start();
You shouldn't use Runtime.exec
. Java 5 introduced a ProcessBuilder which you should use instead. It gives you better control over starting the process and setting the environment.
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "myscript", "arg1", "arg2", "", "arg4");
pb.start();
How about something like this?
javaParams=""
[ -n "$1" ] && javaParams="-a '$1'"
[ -n "$2" ] && javaParams="$javaParams -b '$2'"
[ -n "$3" ] && javaParams="$javaParams -c '$3'"
[ -n "$4" ] && javaParams="$javaParams -d '$4'"
command $javaParams
The
[ -n "$1" ] && javaParams="-a $1"
is equivalent to:
if [ -n "$1" ]
then
javaParams="-a $1"
fi
The -n
test sees if the provided string is zero length or not.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.