Executing a shell script with an empty argument from Java - 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();

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.

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();

Related

Bash command not working with ProcessBuilder

The following command executes fine in bash:
Command:
bash -c "$(echo 'H4sIAArQ/mAAA1WMuw7CIBRAd77ihLJqtKuTg19hHIjetiQU0svl/1sn43weaeKJD4PnlI2R1w1bpOBA3kvF340ssX1Z1LmvUqyhsvWk8jl7nOQmP/2x9ZixSlXWqnLcYvlrw4VwJYxHOiW3AwCHgS2AAAAA' | base64 --decode | zcat)" - -a -b
Output:
Equal to or more than 2 arguments - -a -b
Wanted to know - how can I achieve this using Java's ProcessBuilder?
I tried the following:
ProcessBuilder processBuilder = new ProcessBuilder(args);
where args are:
bash
-c
"$(echo 'H4sIAArQ/mAAA1WMuw7CIBRAd77ihLJqtKuTg19hHIjetiQU0svl/1sn43weaeKJD4PnlI2R1w1bpOBA3kvF340ssX1Z1LmvUqyhsvWk8jl7nOQmP/2x9ZixSlXWqnLcYvlrw4VwJYxHOiW3AwCHgS2AAAAA' | base64 --decode | zcat)"
-
-a
-b
But I keep on getting the following error:
-: if: command not found
Process finished with exit code 127
Can someone please point out the issue here?
Command substitution results, in bash, don't go through all parsing steps. That means that compound commands like if aren't honored, command separators like ; have no syntactic meaning, etc.
If you want to override that and force an additional parsing pass, you need to use eval. Thus:
args = String[]{
"bash",
"-c",
"eval \"$(echo 'H4sIAArQ/mAAA1WMuw7CIBRAd77ihLJqtKuTg19hHIjetiQU0svl/1sn43weaeKJD4PnlI2R1w1bpOBA3kvF340ssX1Z1LmvUqyhsvWk8jl7nOQmP/2x9ZixSlXWqnLcYvlrw4VwJYxHOiW3AwCHgS2AAAAA' | base64 --decode | zcat)\"",
"-",
"-a",
"-b",
}
Why did this work when you ran it in a shell, instead of from a ProcessBuilder? Because that shell you ran it in would perform the command substitution in "$(...)", and put the results of that substitution in the text it passed to the child shell; so the substitution was already done at parsing time.

apache.commons.cli and "long" command-line parameters in Java

Is it possible to use 'long' parameters for command-line options with apache.commons.cli.CommandLineParser in java? 'Long' I mean not a single word, but a sentence in block-quotes ('"'). Java application is being started from bash-script file.
Script file:
#!/bin/bash
java -cp <classpath> MyClass $#
Call (which returns only "long" for '-p' argument and "another" for '-r' argument):
./script.sh -p "long parameter" -r "another long parameter"
Now I can only do something like this to get "long parameter" string:
./script.sh -p "long" -p "parameter"
Of course I can add '-p' programmatically as many times as needed, but this prevents me from using '-' sign in parameter values, as I need to track other command-line switches. Besides this seems to be far not correct approach.
CommandLine options are created like this:
org.apache.commons.cli.Options options = new Options();
options.addOption(Option.builder("p").longOpt("param").hasArg().desc("parameter description").build());
And then parsed like this:
CommandLineParser parser = new DefaultParser();
CommandLine line = parser.parse( options, args );
if (line.hasOption('p')) params = line.getOptionValues("param");
Finally it's got clear (thanks to Jim Garrison) that the problem is caused by calling java class from bash-script (passing all the arguments with $#). Wrapping $# into double quotes in script solved the problem. Like this:
#!/bin/bash
java -cp <path> MyClass "$#"
instead of incorrect
#!/bin/bash
java -cp <path> MyClass $#

Execute bash script and pass arguments with spaces from java

I know there are a lot of post about executing commands from Java but I just can't get this to work. Here is what I'm trying to do, I have a bash script, it receives 2 arguments which might or might not have spaces, then from Java I'm executing the script and passing the arguments like this(I'm surrounding the arguments with quotes and escaping them with backslashes):
String cmd = "/opt/myScript \"/opt/myPath1\" \"/opt/myPath2 with spaces\"";
Runtime rt = Runtime.getRuntime();
rt.exec(cmd);
I also tried to use the ProcessBuilder class like this:
String myScript = "/opt/myScript";
String myArg1= "/opt/myPath1";
String myArg2 = "/opt/myPath2 with spaces";
ProcessBuilder pb = new ProcessBuilder(myScript , myArg1, myArg2);
pb.start;
Arguments with no spaces are received successfully but I still have problems with the second one.
I thought the ProcessBuilder class would handle the spaces but seems like I'm missing something.
I'm not sure if it has something to do, but just in case here is my script:
#!/bin/bash
PATH=$PATH:$1
gnome-terminal --working-directory $2
$1 and $2 are the arguments sent from Java.
Get the same trouble, finally solved with:
Runtime.getRuntime().exec(new String[]{"bash", "-c", <command with spaces>});
Runtime.exec() is an overloaded method. There are several possible ways how to call it. The call exec(String command) executes the specified string command but the argument are separated by spaces here. The method exec(String[] cmdarray) executes the specified command and arguments. There are other exec() variants but the best for you is
String cmd[] = new String[] {"/opt/myScript", "/opt/myPath1", "/opt/myPath2 with spaces" };
Runtime rt = Runtime.getRuntime();
rt.exec(cmd);
It is possible to use ProcessBuilder can be used as well for argument passing. I think the only error is missing parenthesis after pb.start.
And last but not least the script has a major bug. It does not contain quutes arround $2. It should be
#!/bin/bash
PATH="$PATH:$1"
gnome-terminal --working-directory "$2"

java processbuilder ffmpeg pipe

i try to run ffmpeg out java. here my code:
String[] temp = {"ffmpeg\\ffmpeg.exe","-i","input_track.ac3","-threads","0","-af","volume=volume="0.0"dB","-acodec","pcm_s32le","-ac","6","-ar","48000","-f","wav","-","|","ffmpeg\\fdkaac","--ignorelength","-m","1","-o","ouput_track.aac","-"};
ProcessBuilder pb = new ProcessBuilder(temp);
Process p = pb.start();
int ev = 0;
if (p.waitFor() != 0)
{
ev = p.exitValue();
}
i try the comand at windows cmd, here have a problem with "|" at the ffmpeg command line.
maybe someone say my fould?
best regards
This question is similar to How to make pipes work with Runtime.exec()? ... except that it is for Windows.
The problem is essentially the same: the exec methods don't understand shell syntax such as pipes, input or output direction and so on. The solution is essentially the same too: exec the appropriate shell and get that to handle the shell syntax.
In this case, try something like this:
String[] temp = new String[] {
"cmd", "/c",
"ffmpeg\\ffmpeg.exe -i input_track.ac3 -threads 0 " +
"-af volume=volume=\"0.0\"dB -acodec pcm_s32le -ac 6 " +
"-ar 48000 -f wav - | " +
"ffmpeg\\fdkaac --ignorelength -m 1 -o ouput_track.aac -"
};
Note that the actual command is a single string. (The quotes around the 0.0 look a bit strange, but that is what you have in your question.)
| is a shell pipe character, in java you'll have to either run this command in a shall (bash -c "the whole commandline | goes here"), or you'll have to run two processes (the one before the | and the one after), where the stdout of the first writes into the stdin of the second. For this, you'd typically use redirectOutput(Redirect.PIPE) and redirectInput(Redirect.PIPE).

shell script if statement executed from java?

I'm trying to execute unix commands thru a java program. Some of these commands involve an if-then-fi statement. Can this be done thru java / Runtime class? Seems like it only handles 1 command at a time.
I'm looking to do something like this:
grep 'Error One' SystemErr.log > $HOME/tempFiles/output.txt
grep 'Error Two' SystemErr.log >> $HOME/tempFiles/output.txt
grep 'Error Three' SystemErr.log >> $HOME/tempFiles/output.txt
.
.
if [ -s $HOME/tempFiles/output.txt ]
then
mail -s "Subject here" "a#b.com" < $HOME/tempFiles/output.txt
fi
Basically, I just want to email the file (results) if the grep found anything.
I want to use java instead of a direct shell script so that the errors I search for can be database-driven, easier to change.
I know I could read the file myself in java and search/parse it myself. But grep and other unix commands have a lot of built-in functionality I want to use to make it easier.
Any ideas, or am I totally on the wrong track?
Here is some code, using simpler commands, but basically equivalent:
public static void main( String[] args ) throws Exception {
try {
ProcessBuilder pb = new ProcessBuilder( "/bin/bash", "-c",
"echo one >/tmp/xxx && echo two >>/tmp/xxx && " +
"if [ -s /tmp/xxx ]; then cp /tmp/xxx /tmp/yyy; fi" );
File log = new File( "/tmp/log.txt" );
pb.redirectOutput(ProcessBuilder.Redirect.appendTo(log));
Process process = pb.start();
process.waitFor();
} catch( Exception e ){
// ...
} catch( Error e ){
// ...
}
}
The trick is to put it all into a single shell command so that you can call /bin/bash with the -c command option.
If composing this command is too complicated, write a shell file and source that.

Categories