Invoking shell script contains SSH from java - java

I am trying to invoke a shell script containing SSH command from a java program. However it is failing with a error code 1.
My java code is as:
public class CallScript {
private static String filePath="";
private static String args1="";
public static void main(String[] args) throws Exception{
if(args!=null && args.length > 0){
filePath = args[0];
if(args.length > 1){
args1=args[1];
}
}else{
throw new Exception("File Path should be first Argument");
}
System.out.println(args.length);
invokeScript(filePath,args1);
}
private static void invokeScript(String filePath, String arg1) throws Exception{
System.out.println("Inside invoke Script " + arg1);
Process p = Runtime.getRuntime().exec(filePath);
p.waitFor();
int exitVal = p.exitValue();
System.out.println("The Exit Value " + exitVal);
}
}
I compiled the program and placed the executable jar in my Unix environment.
shell script which is invoked from java
ssh -l >test.log
I used the following command to run my java program :
java -jar invokeScript.jar /tmp/upog/test.sh
output
Inside invoke Script
The Exit Value 1.
If I have some other command in the shell script like ls -al > test.log, the code is working with success and I am getting the return value 0.
Also if I invoke the shell script containing ssh command directly in Unix box, it is working fine.(the box have password-less connectivity)
But it is failing, if i call from java...
Any advice....

invokeScript.jar works fine for me here. The issue looks to be with /tmp/upog/test.sh. If I try to run the command I get:
$ ssh -l >test.log
ssh: option requires an argument -- l
[...]
Did you mean ssh -l someuser example.com >test.log?
When I test it with an error-free shell script, running ssh works:
$ cat >/tmp/upog/test.sh
#!/bin/bash
ssh example.com ls /tmp/upog >test.log
$ chmod +x /tmp/upog/test.sh
$ java -jar invokeScript.jar /tmp/upog/test.sh
1
Inside invoke Script
The Exit Value 0
$ cat test.log
bar
baz
foo
(Using example.com as the replacement text for my actual server)

there are two possible return values:
return value of 0 means success
return value of >=1 means an error. One should be seeking for an error code in this case. that is why the error codes are different.
There are two general approaches to use 1 as a flag to show you get an error and the error code itself is in the other place. The other way is to return 0 as success and in case of error is to return an error code itself which is >0. In nix-like systems major command return 0 or 1.
ls -al > test.log
there are two command here, the both are executed with success 0 + 0 - return value 0.
ssh -l >test.log
Here, the first command "ssh -l" executed with an error because you have not provided any arguments. No matter how ">test.log" will go you got 1 already.
your app works ok it seems - it return an exit value.
in case you need more info about the error of nested app.
you may execute
ssh -l >test.log 2>&1 here test.log will have errors.
or you may use something like
BufferedReader err = new BufferedReader(
new InputStreamReader(p.getErrorStream())
); //
in your java app to handle error there.
Note: java code might be incorrect since I use it rarely
Upd: edited command syntax
wrong 2.&1 -> correct 2>&1

Related

Using Java's Runtime.getRuntime().exec I get error with some commands, success with others -- how can this be determined? [duplicate]

When I try to run Runtime.exec(String), certain commands work, while other commands are executed but fail or do different things than in my terminal. Here is a self-contained test case that demonstrates the effect:
public class ExecTest {
static void exec(String cmd) throws Exception {
Process p = Runtime.getRuntime().exec(cmd);
int i;
while( (i=p.getInputStream().read()) != -1) {
System.out.write(i);
}
while( (i=p.getErrorStream().read()) != -1) {
System.err.write(i);
}
}
public static void main(String[] args) throws Exception {
System.out.print("Runtime.exec: ");
String cmd = new java.util.Scanner(System.in).nextLine();
exec(cmd);
}
}
The example works great if I replace the command with echo hello world, but for other commands -- especially those involving filenames with spaces like here -- I get errors even though the command is clearly being executed:
myshell$ javac ExecTest.java && java ExecTest
Runtime.exec: ls -l 'My File.txt'
ls: cannot access 'My: No such file or directory
ls: cannot access File.txt': No such file or directory
meanwhile, copy-pasting to my shell:
myshell$ ls -l 'My File.txt'
-rw-r--r-- 1 me me 4 Aug 2 11:44 My File.txt
Why is there a difference? When does it work and when does it fail? How do I make it work for all commands?
Why do some commands fail?
This happens because the command passed to Runtime.exec(String) is not executed in a shell. The shell performs a lot of common support services for programs, and when the shell is not around to do them, the command will fail.
When do commands fail?
A command will fail whenever it depends on a shell features. The shell does a lot of common, useful things we don't normally think about:
The shell splits correctly on quotes and spaces
This makes sure the filename in "My File.txt" remains a single argument.
Runtime.exec(String) naively splits on spaces and would pass this as two separate filenames. This obviously fails.
The shell expands globs/wildcards
When you run ls *.doc, the shell rewrites it into ls letter.doc notes.doc.
Runtime.exec(String) doesn't, it just passes them as arguments.
ls has no idea what * is, so the command fails.
The shell manages pipes and redirections.
When you run ls mydir > output.txt, the shell opens "output.txt" for command output and removes it from the command line, giving ls mydir.
Runtime.exec(String) doesn't. It just passes them as arguments.
ls has no idea what > means, so the command fails.
The shell expands variables and commands
When you run ls "$HOME" or ls "$(pwd)", the shell rewrites it into ls /home/myuser.
Runtime.exec(String) doesn't, it just passes them as arguments.
ls has no idea what $ means, so the command fails.
What can you do instead?
There are two ways to execute arbitrarily complex commands:
Simple and sloppy: delegate to a shell.
You can just use Runtime.exec(String[]) (note the array parameter) and pass your command directly to a shell that can do all the heavy lifting:
// Simple, sloppy fix. May have security and robustness implications
String myFile = "some filename.txt";
String myCommand = "cp -R '" + myFile + "' $HOME 2> errorlog";
Runtime.getRuntime().exec(new String[] { "bash", "-c", myCommand });
Secure and robust: take on the responsibilities of the shell.
This is not a fix that can be mechanically applied, but requires an understanding the Unix execution model, what shells do, and how you can do the same. However, you can get a solid, secure and robust solution by taking the shell out of the picture. This is facilitated by ProcessBuilder.
The command from the previous example that requires someone to handle 1. quotes, 2. variables, and 3. redirections, can be written as:
String myFile = "some filename.txt";
ProcessBuilder builder = new ProcessBuilder(
"cp", "-R", myFile, // We handle word splitting
System.getenv("HOME")); // We handle variables
builder.redirectError( // We set up redirections
ProcessBuilder.Redirect.to(new File("errorlog")));
builder.start();

How to pass file location(which is owned by different user) as argument to bash script from java code [duplicate]

When I try to run Runtime.exec(String), certain commands work, while other commands are executed but fail or do different things than in my terminal. Here is a self-contained test case that demonstrates the effect:
public class ExecTest {
static void exec(String cmd) throws Exception {
Process p = Runtime.getRuntime().exec(cmd);
int i;
while( (i=p.getInputStream().read()) != -1) {
System.out.write(i);
}
while( (i=p.getErrorStream().read()) != -1) {
System.err.write(i);
}
}
public static void main(String[] args) throws Exception {
System.out.print("Runtime.exec: ");
String cmd = new java.util.Scanner(System.in).nextLine();
exec(cmd);
}
}
The example works great if I replace the command with echo hello world, but for other commands -- especially those involving filenames with spaces like here -- I get errors even though the command is clearly being executed:
myshell$ javac ExecTest.java && java ExecTest
Runtime.exec: ls -l 'My File.txt'
ls: cannot access 'My: No such file or directory
ls: cannot access File.txt': No such file or directory
meanwhile, copy-pasting to my shell:
myshell$ ls -l 'My File.txt'
-rw-r--r-- 1 me me 4 Aug 2 11:44 My File.txt
Why is there a difference? When does it work and when does it fail? How do I make it work for all commands?
Why do some commands fail?
This happens because the command passed to Runtime.exec(String) is not executed in a shell. The shell performs a lot of common support services for programs, and when the shell is not around to do them, the command will fail.
When do commands fail?
A command will fail whenever it depends on a shell features. The shell does a lot of common, useful things we don't normally think about:
The shell splits correctly on quotes and spaces
This makes sure the filename in "My File.txt" remains a single argument.
Runtime.exec(String) naively splits on spaces and would pass this as two separate filenames. This obviously fails.
The shell expands globs/wildcards
When you run ls *.doc, the shell rewrites it into ls letter.doc notes.doc.
Runtime.exec(String) doesn't, it just passes them as arguments.
ls has no idea what * is, so the command fails.
The shell manages pipes and redirections.
When you run ls mydir > output.txt, the shell opens "output.txt" for command output and removes it from the command line, giving ls mydir.
Runtime.exec(String) doesn't. It just passes them as arguments.
ls has no idea what > means, so the command fails.
The shell expands variables and commands
When you run ls "$HOME" or ls "$(pwd)", the shell rewrites it into ls /home/myuser.
Runtime.exec(String) doesn't, it just passes them as arguments.
ls has no idea what $ means, so the command fails.
What can you do instead?
There are two ways to execute arbitrarily complex commands:
Simple and sloppy: delegate to a shell.
You can just use Runtime.exec(String[]) (note the array parameter) and pass your command directly to a shell that can do all the heavy lifting:
// Simple, sloppy fix. May have security and robustness implications
String myFile = "some filename.txt";
String myCommand = "cp -R '" + myFile + "' $HOME 2> errorlog";
Runtime.getRuntime().exec(new String[] { "bash", "-c", myCommand });
Secure and robust: take on the responsibilities of the shell.
This is not a fix that can be mechanically applied, but requires an understanding the Unix execution model, what shells do, and how you can do the same. However, you can get a solid, secure and robust solution by taking the shell out of the picture. This is facilitated by ProcessBuilder.
The command from the previous example that requires someone to handle 1. quotes, 2. variables, and 3. redirections, can be written as:
String myFile = "some filename.txt";
ProcessBuilder builder = new ProcessBuilder(
"cp", "-R", myFile, // We handle word splitting
System.getenv("HOME")); // We handle variables
builder.redirectError( // We set up redirections
ProcessBuilder.Redirect.to(new File("errorlog")));
builder.start();

Return Java system exit value to bash script

I am trying to get the return value from a java program ( System.exit(1);) into a shell script, but it seems like its returning the jvm exit code, which is always 0, if it doesnt crash. For testing purposes, this is the very first line in my main().
Anyone know how to do this?
My bash code:
java bsc/cdisc/ImportData $p $e $t
#-----------------------------------------
# CATCH THE VALUE OF ${?} IN VARIABLE 'STATUS'
# STATUS="${?}"
# ---------------------------------------
STATUS="${?}"
# return to parent directory
cd ../scripts
echo "${STATUS}"
Thanks
If your script has only the two lines then you are not checking for the correct exit code.
I am guessing you are doing something like:
$ java YourJavaBinary
$ ./script
where script contains only:
STATUS="${?}"
echo "${STATUS}"
Here, the script is executed in a subshell. So when you execute the script, $? is the value of last command in that shell which is nothing in the subshell. Hence, it always returns 0.
What you probably wanted to do is to call the java binary in your script itself.
java YourJavaBinary
STATUS="${?}"
echo "${STATUS}"
Or simply check the exit code directly without using the script:
$ java YourJavaBinary ; echo $?
You should do like this:
Test.java:
public class Test{
public static void main (String[] args){
System.exit(2);
}
}
test.sh
#!/bin/bash
java Test
STATUS=$?
echo $STATUS

Runtime.exec not working

I'm trying to run the following code to swap filenames. I'm using Runtime.exec. The code throws IOException. Anyway to fix this?
try {
Runtime.getRuntime().exec("file1=" + folderpath + " && file2=/mnt/sdcard/fsimages && temp=\"$(/system/xbin/mktemp -dp /mnt/sdcard)\" && /system/xbin/mv \"$file1\" $temp && /system/xbin/mv \"$file2\" \"$file1\" && /system/xbin/mv $temp/\"$file1\" \"$file2\"");
} catch (IOException e) {
e.printStackTrace();
return;
}
And the error:
02-28 07:48:02.936: W/System.err(14399): java.io.IOException: Error
running exec(). Command: [file1=/mnt/sdcard/fsimages_3, &&,
file2=/mnt/sdcard/fsimages, &&, temp="$(/system/xbin/mktemp, -dp,
/mnt/sdcard)", &&, /system/xbin/mv, "$file1", $temp, &&,
/system/xbin/mv, "$file2", "$file1", &&, /system/xbin/mv,
$temp/"$file1", "$file2"] Working Directory: null Environment: null
It looks like Runtime.exec is inserting a coma before and after every &&. Seems like the issue is in the way which Runtime.exec interprets &&. Why is this happening? How can I prevent this?
If you use the Runtime.exec(String) overload, the string is treated as a command and its arguments, and is crudely split into substrings at white-space boundaries. This splitting is standard behaviour for that overload. (Refer to the javadoc.)
Runtime.exec(...) expects a native command and its arguments. You've provided a line of shell input. The exec methods don't understand shell input and don't know how to execute it properly. And the crude splitting (see above) messes up everything.
If you need to do that, then use the following:
String yourShellInput = "echo hi && echo ho"; // or whatever ...
String[] commandAndArgs = new String[]{ "/bin/sh", "-c", yourShellInput };
Runtime.getRuntime().exec(commandAndArgs);
This is equivalent to running:
$ /bin/sh -c "echo hi && echo ho".
If sh is not installed as /bin/sh use the path where it is installed instead.
First of all the problem is not what you think "It looks like Runtime.exec is inserting a comma before and after every &&" actually the error statement is reporting your command that you gave in Runtime.exec() as String array (String[]). This is a standard behaviour of Java Runtime.exec() method.
02-28 07:48:02.936:
W/System.err(14399): java.io.IOException:
Error runningexec(). Command:
[file1=/mnt/sdcard/fsimages_3, &&, file2=/mnt/sdcard/fsimages, &&, temp="$(/system/xbin/mktemp, -dp, /mnt/sdcard)", &&, /system/xbin/mv, "$file1", $temp, &&, /system/xbin/mv, "$file2", "$file1", &&, /system/xbin/mv, $temp/"$file1", "$file2"]
Working Directory: null Environment: null
You can see in the error how Java interprets your command which you hardcoded, the String array you are getting back shows you that "file1=/mnt/sdcard/fsimages_3" is treated as opening command to execute and the rest " &&" , "file2=/mnt/sdcard/fsimages" etc. is treated as arguments.
What I think you should do is first try to split your command in an array like structure. as an example below
Process process;
String[] commandAndArgs = new String[]{ "/bin/sh", "mv", "/mnt/sdcard/fsimages_3","/mnt/sdcard/fsimages" };
process = Runtime.getRuntime().exec(commandAndArgs);
Next, your error shows that your working directory is null and your Environment is also null. So you need to set both of them for your process. From Java docs you can see it states this exec() overloaded method
exec(String[] cmdarray, String[] envp, File dir)
Executes the specified command and arguments in a separate process with the specified environment and working directory.
In case nothing stated works, I request you to kindly write a shell script file for your command and then set it executable using
chmod 755 or chmod +x script.sh;
then try to run that. I hope it works as in my case the script file approach worked.

Getting exit value of a shell script 255 every time

I have a jar say test.jar having TestJar as its main class, a shell script jar_executor.sh,and a java file. My test.jar will return 1 if we pass 1 as an argument and 2 if we pass any other value.
My shell script is executing test.jar as follow
#!/usr/bin/ksh
java TestJar 1 -cp test.jar
return_code=$?
exit $return_code
In java file I am creating a process and executing shell script and taking its exitvalue with following code
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(sh jar_executor.sh);
int exitVal = process.waitFor();
System.out.println(exitVal);
This exitVal variable should print 1 or 2 as per argument we are passing but it is printing 255 everytime.
If I use echo $return_code in shell script then I am getting correct value.
Please help me why I am getting value 255 with exit. Thanks in advance!!!
255 or -1 is an application defined exit code, you would have to read the code to know what it means.
A Java application which exits normally, returns 0 and if it throws an Exception, returns 1.
Go to your workspace library, workspace.metadata.plugins\org.eclipse.e4.workbench
and remove the workbench file :) . after that you can restart eclipse
It looks like it might be a Java bug. Others have reported issues similar to what you've seen; see this Java bug report. The Java devs don't think there's a bug but I'm suspicious. The code JRE uses to get the return value from a spawned process is hairy, and I wouldn't be surprised if there's a race condition or other concurrency bug.
I'm guessing that the JRE fails to capture the return code if the spawned process exits very quickly. If my suspicion is correct, adding sleep 1 to your shell script will cause it to work.
Your shell script does not call java correctly:
java TestJar 1 -cp test.jar
You need to set the options for the java command before you mention your main class name. All arguments after your main class name are arguments for your main class and are no longer options for the java VM. So in your case there is no classpath specified for java. I get the following error message when I execute your script: Error: Could not find or load main class TestJar. To fix, you have just to re-order the arguments in your jar_executor.sh script:
java -cp test.jar TestJar 1
I cannot reproduce the 255 on my PC, so I can only guess where that comes from: Either your java command returns an error code of 255 instead of 1 when it fails to load the main class or your korn shell (/usr/bin/ksh) sets this return value when the script is aborted.
Here are all sources I used:
jar_executor.sh
#!/bin/sh -e
java -cp test.jar TestJar 2
return_code=$?
exit $return_code
TestJar.java
public class TestJar {
public static void main(final String[] args) {
System.exit(Integer.parseInt(args[0]));
}
}
JarRunner.java
import java.io.IOException;
public class JarRunner {
public static void main(final String[] args) throws IOException, InterruptedException {
final Runtime runtime = Runtime.getRuntime();
final Process process = runtime.exec("sh jar_executor.sh");
final int exitVal = process.waitFor();
System.out.println(exitVal);
}
}
When I now run java -cp bin JarRunner, I get the output 2 as expected.
You're probably wrong in invoking the script. Try:
Process process = runtime.exec("sh -c jar_executor.sh");
Note the "-c" flag that means you're calling the shell to execute the command.

Categories