简体   繁体   中英

Executing a shell script with an empty argument from Java

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM