How to access the unix shell special variables using java.
Few examples of unix shell special variables:
echo $$ which prints the process ID (PID) of the current shell.
echo $0 which prints the name of the command currently being executed.
echo $? which prints the exit status of the last command executed as a decimal string.
When these shell variables are included in a script file and passed the script file in ProcessBuilder argument, I'm able to execute it successfully using java. But when these variables are passed as arguments, these are not treated as variables itself. Why? How to access these special shell variables?
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class ExecSpecVars {
public static void main(String a[]){
InputStream is = null;
ByteArrayOutputStream baos = null;
List<String> list = new ArrayList<String>();
// String command ="/home/csk/sptest.sh";
String command ="echo $$"; //This should print the PID instead of the string "$$".
String cmd[] = command.split(" ");
for (int i = 0; i < cmd.length; i++) {
list.add(cmd[i]);
}
ProcessBuilder pb = new ProcessBuilder(list);
try {
Process prs = pb.start();
is = prs.getInputStream();
byte[] b = new byte[1024];
int size = 0;
baos = new ByteArrayOutputStream();
while((size = is.read(b)) != -1){
baos.write(b, 0, size);
}
System.out.println(new String(baos.toByteArray()));
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
if(is != null) is.close();
if(baos != null) baos.close();
} catch (Exception ex){}
}
}
}
sptest.sh file contains the below commands:
echo $$
echo $?
echo $0
Run this java class to test the shell special variables in the script file:
uncomment the below line:
String command ="/home/csk/sptest.sh";
Comment the below line:
String command ="echo $$";
This is because the echo command is not resolving the $$ but bash is.
As Java does not run the command in the bash shell, this does not work.
If you run the command in bash then it will work, but it won't return what you might expect; it will return information about bash rather than the Java process:
/bin/bash -c 'echo $$' -> pid of bash process
/bin/bash -c 'echo $?' -> bash exit code
/bin/bash -c 'echo $0' -> /bin/bash
This is because you are now running another command ('/bin/bash') and the information given is about that command rather than your JVM.
In short, there is no easy way to do the things you want in Java.
Here's a quick test case to prove this (code significantly tidied and using Guava):
public static void main(final String[] args) throws Exception {
System.out.println(ManagementFactory.getRuntimeMXBean().getName());
runCommand("/bin/bash", "-c", "echo $$");
runCommand("/bin/bash", "-c", "echo $?");
runCommand("/bin/bash", "-c", "echo $0");
}
private static void runCommand(String... command) throws IOException {
ProcessBuilder pb = new ProcessBuilder(command);
Process prs = pb.start();
try (InputStream is = prs.getInputStream()) {
byte[] b = ByteStreams.toByteArray(is);
System.out.println(new String(b, StandardCharsets.UTF_8));
}
}
Output:
4466#krypto.local
4467
0
/bin/bash
So you can see that the pid is one higher than the JVM pid. And the program name is '/bin/bash'.
Related
I searched a lot but did not find the solution.
My goal is using java to call commands and get output in windows and linux. I found Runtime.exec method and did some experiments.
Everything went ok except when there's space in the command parameters.
Test code as below, also in github.
The code works well on windows, but in linux, output is empty:
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) {
try {
Runtime rt = Runtime.getRuntime();
String[] commandArray;
if (isWindows()) {
commandArray = new String[]{"cmd", "/c", "dir", "\"C:\\Program Files\""};
} else {
commandArray = new String[]{"ls", "\"/root/a directory with space\""};
}
String cmd = String.join(" ",commandArray);
System.out.println(cmd);
Process process = rt.exec(commandArray);
BufferedReader input = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String result = "";
String line = null;
while ((line = input.readLine()) != null) {
result += line;
}
process.waitFor();
System.out.println(result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static boolean isWindows() {
String OS = System.getProperty("os.name").toLowerCase();
return (OS.indexOf("win") >= 0);
}
}
if I execute the printed command in bash directly, then the output is as expected.
[root#localhost javatest]# javac Main.java
[root#localhost javatest]# java Main
ls "/root/a directory with space"
[root#localhost javatest]# ls "/root/a directory with space"
a.txt b.txt
[root#localhost javatest]#
Can anyone explain why and give ways to solve?
There are two versions of exec.
exec(String command)
Here you specify a command in a similar way to how you would do it on the command-line, i.e. you need to quote arguments with spaces.
cmd /c dir "C:\Program Files"
exec(String[] cmdarray)
Here you specify the arguments separately, so the arguments are given as-is, i.e. without quotes. The exec method will take care of any spaces and quote-characters in the argument, correctly quoting and escaping the argument as needed to execute the command.
cmd
/c
dir
C:\Program Files
So, remove the extra quotes you added:
if (isWindows()) {
commandArray = new String[] { "cmd", "/c", "dir", "C:\\Program Files"};
} else {
commandArray = new String[] { "ls", "/root/a directory with space"};
}
I run code on linux ubuntu 17.10
public class TestExec {
public static void main(String[] args) {
try {
Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "ulimit", "-n"});
BufferedReader in = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
this code returns "unlimited"
but whenever I run command from terminal I get 1024.
Why those numbers are different?
You get the same result if you run the same command from the command line:
$ "/bin/sh" "-c" "ulimit" "-n"
unlimited
This is because -c only looks at the argument immediately following it, which is ulimit. The -n is not part of this argument, and is instead instead assigned as a positional parameter ($0).
To run ulimit -n, the -n needs to be part of that argument:
$ "/bin/sh" "-c" "ulimit -n"
1024
In other words, you should be using:
new String[]{"/bin/sh", "-c", "ulimit -n"}
I have written code in below way.
Java program 1(call shell program) => Shell program => Java program 2 (Will perform encryption).
Hence when I run the Java program 1, it should call shell and followed by java program 2. The java program 2 will return the encrypted value to me and same need to be passed to Java program 1.
When execute shell directly from command prompt, I am getting message like below.
[Serverapp01 AESEncryption]$ sh AESEncrypt.sh /keys/keyfile_05.txt texttoencrypt
AESEncrypt.sh: line 28: exit: 0UhSI9LTa/7efiT0quKUsg==: numeric argument required
My aim is to get the value 0UhSI9LTa/7efiT0quKUsg== to my java program 1. But i am not getting this value from shell to java program 1.
Any help is much appreciated.
Java Program 1
package com.abc.encrypt;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class AESEncrypt1 {
public static String callScript(String encryptionScriptPath,String KeyLocation,String textToEncrypt)
{
int value = 0;
String enc ="Error";
try
{
ProcessBuilder pb = new ProcessBuilder(encryptionScriptPath,KeyLocation,textToEncrypt);
Process p = pb.start();
value = p.waitFor();
BufferedReader br=new BufferedReader(
new InputStreamReader(
p.getInputStream()));
String line;
String temp ="";
while((line=br.readLine())!=null){
System.out.println(line);
temp = temp + line;
}
enc=temp;
}
catch (Exception e)
{
//value = 2;
enc = "ErrorOccured.."+e.toString();
}
return enc;
}
}
Shell look like below
unset CLASSPATH
KeyLocation=$1
TextToEncrypt=$2
isolatedjreLocation=/AESEncryption/isolatedjre17
JarLocation=/AESEncryption
CLASSPATH="/AESEncryption/isolatedjre17/jre17/bin"
export CLASSPATH
#echo $CLASSPATH
encryptedValue='ErrorInJava'
#echo 'Call Java Program'
encryptedValue=$($isolatedjreLocation/jre17/bin/java -cp $JarLocation/AESCryptoJava_v1.0.0.jar com.abc.AESEncryption $KeyLocation $TextToEncrypt)
#echo $encryptedValue
exit $encryptedValue
public class RunBashCommand {
public synchronized boolean RunInBash(String command) {
System.out.println("CMD: "+command);
/*String s; not working this code also
Process p;
try {
Process p = Runtime.getRuntime().exec(command);
BufferedReader br = new BufferedReader(
new InputStreamReader(p.getInputStream()));
while ((s = br.readLine()) != null)
System.out.println("line: " + s);
p.waitFor();
System.out.println ("exit: " + p.exitValue());
PrintBufferReader(getError(p));
p.destroy();
} catch (Exception e) {
e.printStackTrace();
}*/
try {
Process p = new ProcessBuilder("/bin/sh", command).start();
/*Process p = new ProcessBuilder("/bin/bash", command).start();*/
PrintBufferReader(getError(p));
/*p.destroy();*/
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
private static BufferedReader getOutput(Process p) {
return new BufferedReader(new InputStreamReader(p.getInputStream()));
}
private static BufferedReader getError(Process p) {
return new BufferedReader(new InputStreamReader(p.getErrorStream()));
}
private void PrintBufferReader(BufferedReader br) throws IOException {
int value = 0;
String s = "";
while((value = br.read()) != -1)
{
char c = (char)value;
s = s+c;
}
System.out.println("EEEE: "+s);
}
}
I tried this code, but it did not work.
following output came:
CMD: cd /home/jeevan/workspace/apb_proj/; source init.csh
EEEE: /bin/sh: cd /home/jeevan/workspace/apb_proj/; source init.csh: No such file or directory
CMD: cd /home/jeevan/workspace/apb_proj/verif/compile/; make clean; make compile; make elab
EEEE: /bin/sh: cd /home/jeevan/workspace/apb_proj/verif/compile/; make clean; make compile; make elab: No such file or directory
CMD: sh /home/jeevan/workspace/apb_proj/verif/test_lib/src/apb_test31/runme.csh
EEEE: /bin/sh: sh /home/jeevan/workspace/apb_proj/verif/test_lib/src/apb_test31/runme.csh: No such file or directory
can some one help?
You're effectively running:
/bin/sh "cd /home/jeevan/workspace/apb_proj/; source init.csh"
When you run /bin/sh this way, it treats its first argument as the name of a file to execute as a shell script. Of course, there's no file named "cd /home/jeevan/workspace/apb_proj/; source init.csh", so you get an error message.
The correct way to invoke sh with a command as an argument is like this:
/bin/sh -c "cd /home/jeevan/workspace/apb_proj/; source init.csh"
Using process builder, you'd do:
Process p = new ProcessBuilder("/bin/sh", "-c", command).start();
The next problem that you're likely to run into is that it appears that the command you're trying to invoke is a csh command, not an sh command. "source" is a csh command, and the file you're trying to source is called "init.csh". So maybe you want to invoke csh instead of sh:
Process p = new ProcessBuilder("/bin/csh", "-c", command).start();
You need to split command arguments into separate parameters: not ProcessBuilder("bin/sh", "cd foo/bar") but ProcessBuilder("bin/sh", "cd", "foo/bar").
You can't use shell metacharacters (like ";") too. To run multiple commands, you have to start multiple processes.
Put all your commands into a List and pass it as the argument to the ProcessBuilder. As an alternative you can start the shell process, get it's OutputStream and write commands into this stream to execute them.
I am trying to execute a shell script with command line arguments using ProcessBuilder, this shell script inturn calls two other shell scripts that uses this argument. The first shell script runs fine, but when the second one is started it returns exit code 1.
ProcessBuilder snippet from Java Program:
//scenario - A string that holds a numerical value like 1 or 2 etc
String[] command2 = {"/bin/bash", "<path to shell script>/runTemporaryTestSuite.sh", scenario};
ProcessBuilder pb2 = new ProcessBuilder(command2);
Process p2 = pb2.start();
BufferedReader br = new BufferedReader(new InputStreamReader(p2.getInputStream()));
String line;
//print - is an object ref of response.getWriter() //
print.println("Output of running "+Arrays.toString(command2)+" is: ");
while ((line = br.readLine()) != null) {
print.println(line);
}
try {
int exitValue = p2.waitFor();
print.println("<br><br>Exit Value of p2 is " + exitValue);
} catch (InterruptedException e) {
e.printStackTrace();
}
runTemporaryTestSuite.sh
#!/bin/bash
sh <path to script>/clearRegressionResult.sh (This runs fine)
sh <path to script>/startRegression.sh $1 (This is where the issue occurs)
startRegression.sh looks like:
SUITE_PATH="./"
java -DconfigPath=${SUITE_PATH}/config.xml -Dscenario=$1 -Dauto=true -jar test.jar
My output:
Output of running [/bin/bash, /runTemporaryTestSuite.sh, 29] is:
Exit Value of p2 is 1
Any help in resolving this is really appreciated.
In think the problem is not that you cannot launch shell script with arguments, I was curious and I did a test
public class Main {
public static void main(String[] args) throws IOException {
String[] command = {"/bin/bash", "test.sh", "Argument1"};
ProcessBuilder p = new ProcessBuilder(command);
Process p2 = p.start();
BufferedReader br = new BufferedReader(new InputStreamReader(p2.getInputStream()));
String line;
System.out.println("Output of running " + command + " is: ");
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
here is the test.sh script
echo Hello im the script, here your args $#
Here the output
Output of running [Ljava.lang.String;#604e9f7f is:
Hello im the script, here your args Argument1
What I think is just that your startRegression.sh exit with a non-0 status (aka it failed somewhere) and it have repercussion, runTemporaryTestSuite.sh will also exit with a non-zero status, and so on hence the message : Exit Value of p2 is 1
What I see right now,
SUITE_PATH="./"
java -DconfigPath=${SUITE_PATH}/config.xml [..] the configPath will be .//config.xml so maybe you have a plain file not found issue? I might be wrong, hope it helped