ProcessBuilder failing when calling a system command where Runtime exec works - java

I'm having trouble with ProcessBuilder not running a command on the server.
Early in my project I use Runtime.exec() just to retrieve output from a program which works fine:
private List<SatelliteCode> getSatelliteCodes() {
List<SatelliteCode> codes = new ArrayList<>();
Runtime runtime = Runtime.getRuntime();
String[] commands = { "w_scan", "-s?" };
Process process;
try {
process = runtime.exec(commands);
BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s = error.readLine(); // discard first line
while ((s = error.readLine()) != null) {
s = s.trim();
int i = s.indexOf('\t'); // separated by a tab!?!?
codes.add(new SatelliteCode(s.substring(0, i), s.substring(i)));
}
} catch (IOException e) {
e.printStackTrace();
}
return codes;
}
Running this in the terminal works fine and I get all the output I need:
w_scan -fs -cGB -sS19E2 > channels.conf
However, the server needs to grab the ongoing output from the 'process.getErrorStream()' to display in the web interface. What is actually happening is the ProcessBuilder is failing and returning an exit code of 1.
The function that initialises the ProcessBuilder and to start the scan running is [EDIT 1]:
private static StringBuilder scan_error_output = null;
#Override
public boolean startSatelliteScan(String user, String country_code, String satellite_code) {
UserAccountPermissions perm = validateUserEdit(user);
if (perm == null) return false;
Shared.writeUserLog(user, Shared.getTimeStamp() +
": DVB satellite scan started " +
country_code + " - " + satellite_code +
System.lineSeparator() + System.lineSeparator());
scan_error_output = new StringBuilder();
new ScanThread(country_code, satellite_code).start();
// write out country code and satellite code to prefs file
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(satellite_last_scan_codes));
bw.write(country_code); bw.newLine();
bw.write(satellite_code); bw.newLine();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
That will then run two other threads on the server, one that will run the scan itself and wait for it to finish so that it can get the final scan data. And the other which constantly updates the output from the std error stream which is then polled at intervals from the clients browser. This is much like showing the ongoing output from the terminal.
The scan thread (which fails to start the process) [EDIT 1]:
private static class ScanThread extends Thread {
private String cc, sc;
public ScanThread(String country_code, String satellite_code) {
cc = country_code;
sc = satellite_code;
}
public void run() {
ProcessBuilder pb = new ProcessBuilder("/usr/bin/w_scan",
"-fs", "-c" + cc, "-s" + sc);
pb.redirectOutput(new File(satellite_scan_file));
Process process;
try {
System.out.println("Scan thread started");
process = pb.start();
IOScanErrorOutputHandler error_output_handler = new IOScanErrorOutputHandler(process.getErrorStream());
error_output_handler.start();
int result = process.waitFor();
System.out.println(cc + " - " + sc + " - " +
"Process.waitFor() result " + result);
} catch (IOException e) {
System.out.println(e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
System.out.println("Scan thread finished");
}
}
The error output stream thread which captures the output which obviously doesn't start due to the scan thread failing:
private static class IOScanErrorOutputHandler extends Thread {
private InputStream inputStream;
IOScanErrorOutputHandler(InputStream inputStream) {
this.inputStream = inputStream;
}
public void run() {
Scanner br = null;
try {
System.out.println("Scan thread Error IO capture running");
br = new Scanner(new InputStreamReader(inputStream));
String line = null;
while (br.hasNextLine()) {
line = br.nextLine();
scan_error_output.append(line + System.getProperty("line.separator"));
}
} finally {
br.close();
}
System.out.println("Scan thread Error IO capture finished");
scan_error_output = null;
}
}
And the server function which returns the std error output progress:
#Override
public String pollScanResult(String user) {
if (validateUserEdit(user) == null) return null;
StringBuilder sb = scan_error_output; // grab instance
if (sb == null) return null;
return sb.toString();
}
As mentioned above, Runtime.exec() works fine, but the ProcessBuilder is failing.
NB: I'm on Linux Mint 18.1, using Apache Tomcat 8 as the server, linux default JDK 8 and GWT 2.7 [Correction from 2.8] in Eclipse Neon.
Can anyone see what I am doing wrong?
Many thanks in advance...
[EDIT 1]
Whilst developing this on another machine, Linux Mint 17.2, JDK 8 and Apache Tomcat 7, for DVB-T, this method worked fine and polling for the scan output showed up in the client's browser.
The ProcessBuilder.start still returns 1 and an empty file is created for the output scan file.
[EDIT 2]
It appears that the reason the ProcessBuilder is failing is because the user 'tomcat8' doesn't have permissions to run 'w_scan'. 'w_scan' works from the terminal, but not from the tomcat server. Somehow I've got to fix that now.
[SOLUTIONS]
After being put in the right direction by VGR for getting the error stream from the ProcessBuilder, I started digging further and found I was getting:
main:3909: FATAL: failed to open '/dev/dvb/adapter0/frontend0': 13 Permission denied
Apache tomcat 8 didn't have permission to access the DVB-S frontend to run a scan. This was fixed in two ways:
1 - 03catalina.policy I added the extra permissions (whether they made a difference I do not know).
grant codeBase "file:/dev/dvb/-" {
permission java.io.FilePermission "file:/dev/dvb/-", "read, write";
permission java.security.AllPermission;
};
2 - The dvb frontends belong to the 'video' group. So I needed to add the user tomcat8 to that group.
usermod -a -G video tomcat8
All works for now...

You are not doing the same thing with ProcessBuilder that you’re doing with Runtime.exec, so I don't know why you think ProcessBuilder is the problem.
You have a few problems with how you’re writing the command’s output to a file.
First, the presence of ">", satellite_scan_temp_file in your ProcessBuilder command is incorrect. Output redirection is not part of any command; it is handled by a shell. But when you run with Runtime.exec or ProcessBuilder, you are not running in a shell, you are executing the process directly. Neither w_scan nor any other command considers > a special character.
The correct way to redirect to a file is with the redirectOutput method:
ProcessBuilder pb = new ProcessBuilder(
"/usr/bin/w_scan", "-fs", "-s" + satellite_code, "-c" + country_code);
pb.redirectOutput(new File(satellite_scan_temp_file));
Second, your ScanThread code is ignoring the (currently incorrect) redirect, and is attempting to read the command’s output. But there is no output, because you are redirecting it all to a file.
Once you are properly redirecting output to a file, you can remove your BufferedReader and BufferedWriter loops completely.
Finally, it is worth noting that the error output you captured probably told you that > is not a valid argument to the w_scan process.

Related

Java Process getting stopped when run in background in linux

I have the below lines of code `
private String build(String command) {
ShellExecutable obj = new ShellExecutable();
String output = obj.executeCommand(command);
return output;
}
private String executeCommand(String command) {
StringBuffer output = new StringBuffer();
Process p;
String[] cmdarray = { "bash", "-c", command };
try {
System.out.println("Before Command Execution in Bash..& command is: " + command);
p = Runtime.getRuntime().exec(cmdarray);
System.out.println("After Command execution in Bash & Before waitFor..");
p.waitFor();
System.out.println("After wait for: " + p.exitValue());
System.out.println("After wait for: " + p.isAlive());
System.out.println("After Command execution in Bash..");
if (p.getInputStream() != null) {
System.out.println("Input Stream is present");
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
output.append(line + "\n");
}
}
if (p.getErrorStream() != null) {
System.out.println("Error Stream is present");
BufferedReader errorReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String errorLine = "";
while ((errorLine = errorReader.readLine()) != null) {
output.append(errorLine + "\n");
}
}
} catch (Exception e) {
System.out.println("Exception Occured:" + e.getLocalizedMessage() + "Message is:" + e.getMessage());
}
return output.toString();
}
`
I am trying to run this as a foreground process in Linux, it works brilliant. But, when I try to run the same as a background process using nohup the service is stopping. I found similar kind of issues on stack overflow but I couldn't't figure out the solution for this particular case.
For the above code the output I am getting is as follows:
Called listApps...
Before Command Execution in Bash..& command is: xxxxxxxx
After Command execution in Bash & Before waitFor..
[1]+ Stopped nohup java -jar ReadingShell-0.0.1-SNAPSHOT-jar-with-dependencies.jar
I am not getting any exception in the above code, its just stopping without displaying anything. But, when I try displaying p.exitValue() before p.waitFor(), I printed the stacktrace, it is as follows,
java.lang.IllegalThreadStateException: process hasn't exited
at java.lang.UNIXProcess.exitValue(UNIXProcess.java:424)
at org.monitoring.ReadingShell.ShellExecutable.executeCommand(ShellExecutable.java:101)
at org.monitoring.ReadingShell.ShellExecutable.build(ShellExecutable.java:82)
at org.monitoring.ReadingShell.ShellExecutable.getApplicationList(ShellExecutable.java:46)
at spark.RouteImpl$1.handle(RouteImpl.java:72)
at spark.http.matching.Routes.execute(Routes.java:61)
at spark.http.matching.MatcherFilter.doFilter(MatcherFilter.java:130)
at spark.embeddedserver.jetty.JettyHandler.doHandle(JettyHandler.java:50)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1568)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:564)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:317)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:110)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124)
at org.eclipse.jetty.util.thread.Invocable.invokePreferred(Invocable.java:128)
at org.eclipse.jetty.util.thread.Invocable$InvocableExecutor.invoke(Invocable.java:222)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:294)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:126)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:590)
at java.lang.Thread.run(Thread.java:745)
You have to read the output streams before you wait for the end of the child process. Otherwise if the child writes more than a buffer's worth (512B? 4K?) to one of the streams, it will be made to wait until something reads and empties the buffer. But this won't happen since your parent process is already executing waitFor().
So, you have to create two threads to read these output streams, and start them before calling waitFor().
By reading the opendjk source code for UnixProcess we see the following
public synchronized int waitFor() throws InterruptedException {
while (!hasExited) {
wait();
}
return exitcode;
}
public synchronized int exitValue() {
if (!hasExited) {
throw new IllegalThreadStateException("process hasn't exited");
}
return exitcode;
}
hasExited is never reset in the file so logically exitValue() cannot throw after waitFor() is called. (Unless it is interrupted)
Something must be different when you run it than in the code your question has. A minimal complete example class displaying the issue so we can reproduce it would help.
I had a similar issue. The jar file would be fine if ran in the foreground, but stop when executed in nohup with the process/job going into Stopped.
Figured out that nohup will go into stopped if any of the inner scripts try to read from the terminal - see the post here
I used tmux to solve this as suggested by this thread

Execute ADB command from Java program

The program I'm working on uses ADB (Android Debug Bridge) to send files to my phone:
for (String s : files)
String cmd = "adb -s 0123456789ABCDEF push " + s + " /mnt/sdcard/" + s;
try {
InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
while (is.read() != -1) {}
} catch (IOException e) {
e.printStackTrace();
}
I want the program to wait until ADB finished the transmission, but ADB runs as a daemon and therefore never finishes. But the program continues immideately and somehow the files aren't sent to my phone (no exceptions in log). When I run the command from console, it's working without problems.
What am I doing wrong? How do I send files via ADB correctly?
NOTE: the is.read() == -1 won't work, because the ADB daemon writes all output to the system standard output. I've tried forwarding it into a textfile. It stayed empty and the output was still written to the terminal instead
EDIT: Reading the ErrorStream of the ADB process returned the adb help for each adb push-command. Again: The exact commands (copied from Eclipse console) work in a terminal
EDIT 2: Using a ProcessBuilder instead of RUntime.getRuntime.exec() resulted in the following error:
java.io.IOException: Cannot run program "adb -s 0123456789ABCDEF push "inputfile "outputfile""": error=2, File or directory not found
at the ProcessBuilder's start()-method
The same happens when using an absolute path for ADB (/usr/bin/adb). The inputfile and outputfile Strings are also absolute paths, like /home/sebastian/testfile and definitely exist. When running the commands from terminal (string "cmd" printed, copy&paste), evreything still works fine.
I finally got it working:
ProcessBuilder pb = new ProcessBuilder("adb", "-s", "0123456789ABCDEF", "push", inputfile, outputfile);
Process pc = pb.start();
pc.waitFor();
System.out.println("Done");
I don't know what problems ProcessBuilder has with spaces in a string, but finally, it's working...
I've solved in this way:
public class Utils {
private static final String[] WIN_RUNTIME = { "cmd.exe", "/C" };
private static final String[] OS_LINUX_RUNTIME = { "/bin/bash", "-l", "-c" };
private Utils() {
}
private static <T> T[] concat(T[] first, T[] second) {
T[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
public static List<String> runProcess(boolean isWin, String... command) {
System.out.print("command to run: ");
for (String s : command) {
System.out.print(s);
}
System.out.print("\n");
String[] allCommand = null;
try {
if (isWin) {
allCommand = concat(WIN_RUNTIME, command);
} else {
allCommand = concat(OS_LINUX_RUNTIME, command);
}
ProcessBuilder pb = new ProcessBuilder(allCommand);
pb.redirectErrorStream(true);
Process p = pb.start();
p.waitFor();
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String _temp = null;
List<String> line = new ArrayList<String>();
while ((_temp = in.readLine()) != null) {
System.out.println("temp line: " + _temp);
line.add(_temp);
}
System.out.println("result after command: " + line);
return line;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
If you don't need env variables in your .bash_profile cut "-l" parameter.
I have a Mac but it should work on Linux also.
public static void adbpush() {
System.out.println("adb push....");
String[] aCommand = new String[] { adbPath, "push", inputFile(String),OutputDirectory };
try {
// Process process = new ProcessBuilder(aCommand).start();
Process process = Runtime.getRuntime().exec(aCommand);
process.waitFor(3, TimeUnit.SECONDS);
System.out.println("file pushed");
} catch (Exception e) {
e.printStackTrace();
}
}
It will be better to give full path for ADB execution: like this $ANDROID_HOME/platform-tools/adb devices
This is the full code you can use:
String cmd = "$ANDROID_HOME/platform-tools/adb devices";
ProcessBuilder processBuilder = new ProcessBuilder();
if (Config.osName.contains("Windows"))
processBuilder.command("cmd.exe", "/c", cmd);
else
processBuilder.command("bash", "-c", cmd);
Process process = processBuilder.start();
Process process = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(process.getOutputStream());
string cmd = "/system/bin/input keyevent 23\n";
os.writeBytes(cmd);
the phone must be rooted. here I have executed adb command "input keyevent 23". remember when you execute adb command through su you does not need to add "adb shell input keyevent 23"

Java execute process on linux

I've been struggling for a while now with this problem and i can't seem to fix it.
i already have tried different approaches (Runtime.exec(), ProcessBuiler) but none seem to work.
This is my issue.
I have a laptop which is always on. This laptop runs a java tool connected to an arduino via usb to turn on and off the lights in the house. i have created this program myself, therefore i'm also doing some regular maintenance work on it. Recently i have added a button to restart the program from my html interface (in case i have an update, or if for some other reason i might need to restart the program or i decide to implement auto updating in the near future).
This idea behind this is to start a second instance of the application from the first instance and then System.exit(0) the first instance.
For some reason i'm not able to start a second instance of the application.
Here's some code.
public void shutdown(boolean restart) {
if (this.serial != null) {
this.serial.disconnect();
}
if (restart) {
System.out.println(this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
String startupCommand = "java -jar \"" + this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath().replace("%20", " ") + "\"";
ProcessBuilder builder = new ProcessBuilder();
// String[] command = new String[1];
// command[0] = "-jar \"" + (System.getProperty("user.dir") + "/Home_Automation_Executor.jar") + "\"";
try {
// //System.out.println("Restarting Home Automation with command: " + command[0]);
// System.out.println("Restarting Home Automation with command: " + startupCommand);
// Runtime.getRuntime().exec("bash");
// Process proc = Runtime.getRuntime().exec(startupCommand);
Process proc = builder.command(startupCommand).start();
InputStream stderr = proc.getErrorStream();
InputStreamReader isr = new InputStreamReader(stderr);
BufferedReader br = new BufferedReader(isr);
String line = null;
System.out.println("<ERROR>");
while ((line = br.readLine()) != null) {
System.out.println(line);
}
System.out.println("</ERROR>");
int exitVal = 0;
try {
exitVal = proc.waitFor();
} catch (InterruptedException ex) {
Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("Process exitValue: " + exitVal);
} catch (IOException ex) {
ex.printStackTrace();
}
}
System.out.println("Terminating Home Automation");
System.exit(0);
}
java.io.IOException: Cannot run program "java -jar "/Users/NightWalker/Dropbox/Development/Source Code/Java/NightWare Tools/Home Automation/Home Automation Executor/dist/Home_Automation_Executor.jar"": error=2, No such file or directory
at java.lang.ProcessBuilder.start(ProcessBuilder.java:460)
at home.automation.executor.Engine.shutdown(Engine.java:186)
at home.automation.executor.webserver.HTTPGenerator._handleActionCommand(HTTPGenerator.java:190)
at home.automation.executor.webserver.HTTPGenerator._generateHTTPPage(HTTPGenerator.java:165)
at home.automation.executor.webserver.HTTPGenerator.getHTTPPage(HTTPGenerator.java:58)
at home.automation.executor.webserver.HTTPRequestHandler.run(HTTPRequestHandler.java:160)
Caused by: java.io.IOException: error=2, No such file or directory
at java.lang.UNIXProcess.forkAndExec(Native Method)
at java.lang.UNIXProcess.(UNIXProcess.java:53)
at java.lang.ProcessImpl.start(ProcessImpl.java:91)
at java.lang.ProcessBuilder.start(ProcessBuilder.java:453)
... 5 more
The problem is this:
String startupCommand = "java -jar \"" + this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath().replace("%20", " ") + "\"";
/* more stuff */ builder.command(startupCommand);
This means Jav will look for a command named java -jar ...stuff with spaces.... But what you want is, that Java looks for a command named java and give that command several parameters.
You should use
/*...*/ builder.command("java", "-jar", jarLocation) /*...*/
Since it is another Java program you might want to consider running it in the same process because it's much easier to communicate between the two programs if they live in the same process. Have you tried running the command outside your program? Does it work? What does the meta-inf.mf file in the jar hold? It might be that the classpath in the meta-inf.mf file isn't relative so any dependent jars can't be found.

Redirect a system call output to a file with Java

Currently having troubles to redirect the output of a small windows batch console to a log file. My Java application needs to start the Runtime.exec() call without waiting it to finish and still log the output. Here is my logger class :
public class BatchThreadLogger extends Thread {
private Process process;
private String logFilePath;
private static final Logger logger = Logger.getLogger(BatchThreadLogger.class);
public BatchThreadLogger(Process process, String logFilePath) {
this.process = process;
this.logFilePath = logFilePath;
}
public void run() {
try {
// create logging file
File file = new File(logFilePath);
file.createNewFile();
// create a writer object
OutputStream os = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(os);
// catch the process output in an InputStream
InputStreamReader isr = new InputStreamReader(process.getInputStream());
BufferedReader br = new BufferedReader(isr);
// wait for the process to complete
int processStatus = process.waitFor();
// redirect the output to the log file
String line = null;
while ((line = br.readLine()) != null) {
pw.println(line);
}
// add a small message with the return code to the log
pw.println("********************************************");
pw.println("********************************************");
pw.println("Batch call completed with return status " + processStatus);
pw.flush();
os.close();
}
catch (IOException e) {
logger.error("IOException raised during batch logging on file " + logFilePath, e);
}
catch (InterruptedException e) {
logger.error("InterruptedException raised during batch process execution", e);
}
}
}
My call is quite simple :
Process process = Runtime.getRuntime().exec(command);
BatchThreadLogger logger = new BatchThreadLogger(process, logFilePath);
logger.start();
My command is currently just calling my test.bat with two parameters. My test batch now just do :
echo "BATCH CALLED WITH PARAMETER %1 AND %2"
exit
My log file however does contains only :
********************************************
********************************************
Batch call completed with return status 0
I tried to place the waitFor()call before and after the code redirecting the output to the log file, without success. I always see the black screen of the command being launched, but nothing in logs...
Any help would be greatly appreciated, I'm missing something, but cannot understand what...
You're not reading from the standard error of the process you create.
I suspect that an error message is being written to the standard error, and because you're only reading from standard output, you're not picking up this error.
I would recommend replacing your use of Runtime.getRuntime().exec(...) with a ProcessBuilder, using something like the following:
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "test.bat", "One", "Two");
pb.redirectErrorStream(true);
Process process = pb.start();
The line pb.redirectErrorStream(true); redirects the process' standard error into its standard output, so that you don't have to read from two streams (standard output and standard error) in two separate threads.

How to run Unix shell script from Java code?

It is quite simple to run a Unix command from Java.
Runtime.getRuntime().exec(myCommand);
But is it possible to run a Unix shell script from Java code? If yes, would it be a good practice to run a shell script from within Java code?
You should really look at Process Builder. It is really built for this kind of thing.
ProcessBuilder pb = new ProcessBuilder("myshellScript.sh", "myArg1", "myArg2");
Map<String, String> env = pb.environment();
env.put("VAR1", "myValue");
env.remove("OTHERVAR");
env.put("VAR2", env.get("VAR1") + "suffix");
pb.directory(new File("myDir"));
Process p = pb.start();
You can use Apache Commons exec library also.
Example :
package testShellScript;
import java.io.IOException;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
public class TestScript {
int iExitValue;
String sCommandString;
public void runScript(String command){
sCommandString = command;
CommandLine oCmdLine = CommandLine.parse(sCommandString);
DefaultExecutor oDefaultExecutor = new DefaultExecutor();
oDefaultExecutor.setExitValue(0);
try {
iExitValue = oDefaultExecutor.execute(oCmdLine);
} catch (ExecuteException e) {
System.err.println("Execution failed.");
e.printStackTrace();
} catch (IOException e) {
System.err.println("permission denied.");
e.printStackTrace();
}
}
public static void main(String args[]){
TestScript testScript = new TestScript();
testScript.runScript("sh /root/Desktop/testScript.sh");
}
}
For further reference, An example is given on Apache Doc also.
I think you have answered your own question with
Runtime.getRuntime().exec(myShellScript);
As to whether it is good practice... what are you trying to do with a shell script that you cannot do with Java?
I would say that it is not in the spirit of Java to run a shell script from Java. Java is meant to be cross platform, and running a shell script would limit its use to just UNIX.
With that said, it's definitely possible to run a shell script from within Java. You'd use exactly the same syntax you listed (I haven't tried it myself, but try executing the shell script directly, and if that doesn't work, execute the shell itself, passing the script in as a command line parameter).
Yes it is possible to do so. This worked out for me.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.omg.CORBA.portable.InputStream;
public static void readBashScript() {
try {
Process proc = Runtime.getRuntime().exec("/home/destino/workspace/JavaProject/listing.sh /"); //Whatever you want to execute
BufferedReader read = new BufferedReader(new InputStreamReader(
proc.getInputStream()));
try {
proc.waitFor();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
while (read.ready()) {
System.out.println(read.readLine());
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
Here is my example. Hope it make sense.
public static void excuteCommand(String filePath) throws IOException{
File file = new File(filePath);
if(!file.isFile()){
throw new IllegalArgumentException("The file " + filePath + " does not exist");
}
if(isLinux()){
Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", filePath}, null);
}else if(isWindows()){
Runtime.getRuntime().exec("cmd /c start " + filePath);
}
}
public static boolean isLinux(){
String os = System.getProperty("os.name");
return os.toLowerCase().indexOf("linux") >= 0;
}
public static boolean isWindows(){
String os = System.getProperty("os.name");
return os.toLowerCase().indexOf("windows") >= 0;
}
Yes, it is possible and you have answered it! About good practises, I think it is better to launch commands from files and not directly from your code. So you have to make Java execute the list of commands (or one command) in an existing .bat, .sh , .ksh ... files.
Here is an example of executing a list of commands in a file MyFile.sh:
String[] cmd = { "sh", "MyFile.sh", "\pathOfTheFile"};
Runtime.getRuntime().exec(cmd);
To avoid having to hardcode an absolute path, you can use the following method that will find and execute your script if it is in your root directory.
public static void runScript() throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder("./nameOfScript.sh");
//Sets the source and destination for subprocess standard I/O to be the same as those of the current Java process.
processBuilder.inheritIO();
Process process = processBuilder.start();
int exitValue = process.waitFor();
if (exitValue != 0) {
// check for errors
new BufferedInputStream(process.getErrorStream());
throw new RuntimeException("execution of script failed!");
}
}
As for me all things must be simple.
For running script just need to execute
new ProcessBuilder("pathToYourShellScript").start();
The ZT Process Executor library is an alternative to Apache Commons Exec. It has functionality to run commands, capturing their output, setting timeouts, etc.
I have not used it yet, but it looks reasonably well-documented.
An example from the documentation: Executing a command, pumping the stderr to a logger, returning the output as UTF8 string.
String output = new ProcessExecutor().command("java", "-version")
.redirectError(Slf4jStream.of(getClass()).asInfo())
.readOutput(true).execute()
.outputUTF8();
Its documentation lists the following advantages over Commons Exec:
Improved handling of streams
Reading/writing to streams
Redirecting stderr to stdout
Improved handling of timeouts
Improved checking of exit codes
Improved API
One liners for quite complex use cases
One liners to get process output into a String
Access to the Process object available
Support for async processes ( Future )
Improved logging with SLF4J API
Support for multiple processes
This is a late answer. However, I thought of putting the struggle I had to bear to get a shell script to be executed from a Spring-Boot application for future developers.
I was working in Spring-Boot and I was not able to find the file to be executed from my Java application and it was throwing FileNotFoundFoundException. I had to keep the file in the resources directory and had to set the file to be scanned in pom.xml while the application was being started like the following.
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
<include>**/*.sh</include>
</includes>
</resource>
</resources>
After that I was having trouble executing the file and it was returning error code = 13, Permission Denied. Then I had to make the file executable by running this command - chmod u+x myShellScript.sh
Finally, I could execute the file using the following code snippet.
public void runScript() {
ProcessBuilder pb = new ProcessBuilder("src/main/resources/myFile.sh");
try {
Process p;
p = pb.start();
} catch (IOException e) {
e.printStackTrace();
}
}
Hope that solves someone's problem.
Here is an example how to run an Unix bash or Windows bat/cmd script from Java. Arguments can be passed on the script and output received from the script. The method accepts arbitrary number of arguments.
public static void runScript(String path, String... args) {
try {
String[] cmd = new String[args.length + 1];
cmd[0] = path;
int count = 0;
for (String s : args) {
cmd[++count] = args[count - 1];
}
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
try {
process.waitFor();
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
while (bufferedReader.ready()) {
System.out.println("Received from script: " + bufferedReader.readLine());
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
System.exit(1);
}
}
When running on Unix/Linux, the path must be Unix-like (with '/' as separator), when running on Windows - use '\'. Hier is an example of a bash script (test.sh) that receives arbitrary number of arguments and doubles every argument:
#!/bin/bash
counter=0
while [ $# -gt 0 ]
do
echo argument $((counter +=1)): $1
echo doubling argument $((counter)): $(($1+$1))
shift
done
When calling
runScript("path_to_script/test.sh", "1", "2")
on Unix/Linux, the output is:
Received from script: argument 1: 1
Received from script: doubling argument 1: 2
Received from script: argument 2: 2
Received from script: doubling argument 2: 4
Hier is a simple cmd Windows script test.cmd that counts number of input arguments:
#echo off
set a=0
for %%x in (%*) do Set /A a+=1
echo %a% arguments received
When calling the script on Windows
runScript("path_to_script\\test.cmd", "1", "2", "3")
The output is
Received from script: 3 arguments received
It is possible, just exec it as any other program. Just make sure your script has the proper #! (she-bang) line as the first line of the script, and make sure there are execute permissions on the file.
For example, if it is a bash script put #!/bin/bash at the top of the script, also chmod +x .
Also as for if it's good practice, no it's not, especially for Java, but if it saves you a lot of time porting a large script over, and you're not getting paid extra to do it ;) save your time, exec the script, and put the porting to Java on your long-term todo list.
I think with
System.getProperty("os.name");
Checking the operating system on can manage the shell/bash scrips if such are supported.
if there is need to make the code portable.
String scriptName = PATH+"/myScript.sh";
String commands[] = new String[]{scriptName,"myArg1", "myArg2"};
Runtime rt = Runtime.getRuntime();
Process process = null;
try{
process = rt.exec(commands);
process.waitFor();
}catch(Exception e){
e.printStackTrace();
}
Just the same thing that Solaris 5.10 it works like this ./batchstart.sh there is a trick I don´t know if your OS accept it use \\. batchstart.sh instead. This double slash may help.
for linux use
public static void runShell(String directory, String command, String[] args, Map<String, String> environment)
{
try
{
if(directory.trim().equals(""))
directory = "/";
String[] cmd = new String[args.length + 1];
cmd[0] = command;
int count = 1;
for(String s : args)
{
cmd[count] = s;
count++;
}
ProcessBuilder pb = new ProcessBuilder(cmd);
Map<String, String> env = pb.environment();
for(String s : environment.keySet())
env.put(s, environment.get(s));
pb.directory(new File(directory));
Process process = pb.start();
BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedWriter outputReader = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
int exitValue = process.waitFor();
if(exitValue != 0) // has errors
{
while(errReader.ready())
{
LogClass.log("ErrShell: " + errReader.readLine(), LogClass.LogMode.LogAll);
}
}
else
{
while(inputReader.ready())
{
LogClass.log("Shell Result : " + inputReader.readLine(), LogClass.LogMode.LogAll);
}
}
}
catch(Exception e)
{
LogClass.log("Err: RunShell, " + e.toString(), LogClass.LogMode.LogAll);
}
}
public static void runShell(String path, String command, String[] args)
{
try
{
String[] cmd = new String[args.length + 1];
if(!path.trim().isEmpty())
cmd[0] = path + "/" + command;
else
cmd[0] = command;
int count = 1;
for(String s : args)
{
cmd[count] = s;
count++;
}
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedWriter outputReader = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
int exitValue = process.waitFor();
if(exitValue != 0) // has errors
{
while(errReader.ready())
{
LogClass.log("ErrShell: " + errReader.readLine(), LogClass.LogMode.LogAll);
}
}
else
{
while(inputReader.ready())
{
LogClass.log("Shell Result: " + inputReader.readLine(), LogClass.LogMode.LogAll);
}
}
}
catch(Exception e)
{
LogClass.log("Err: RunShell, " + e.toString(), LogClass.LogMode.LogAll);
}
}
and for usage;
ShellAssistance.runShell("", "pg_dump", new String[]{"-U", "aliAdmin", "-f", "/home/Backup.sql", "StoresAssistanceDB"});
OR
ShellAssistance.runShell("", "pg_dump", new String[]{"-U", "aliAdmin", "-f", "/home/Backup.sql", "StoresAssistanceDB"}, new Hashmap<>());

Categories

Resources