I'm trying to run a bash script from a Java program I'm writing in windows. I've been trying to use the Runtime object to get a process to work, and my program compiles and runs without exceptions, but my simple test script, which just makes a directory, is not being executed.
Here's what I've got so far:
String cmmd[] = new String[3];
cmmd[0] = "C:\\cygwin\\bin\\bash.exe";
cmmd[1] = "cd C:/Users/pro-services/Desktop/projects/github/cygwin";
cmmd[2] = "bash TEST.sh";
Runtime rt= Runtime.getRuntime();
Process proc = rt.exec(cmmd);
This is basically a mix of different things I've found in forums around the net, but I guess I just don't really understand what is happening with the Process class (and I only have a basic idea about the Runtime class).
I also found this, and plugged my own stuff in where I thought it should go:
Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\bash.exe",
"-c", "c:\\cygwin\\bin\\run.exe -p /bin bash C:\\Users\\pro-services\\Desktop\\projects\\github\\cygwin\\TEST.sh"},
new String[]{"PATH=/cygdrive/c/cygwin/bin"});
Here I'm not sure what the "-c" and "-p" strings represent, but I just went with it. At first it looked like I could just plug in the sequential commands that I want the Runtime/Process object to execute, in essence creating a "script" to run my script. But it now seems that there's more to it...
I'm just shooting in the dark at this point, and I have tried to understand the documentation but I'm lost. Any help would be appreciated. Thanks )))
Untested, but I would think:
cmmd[0] = "C:/cygwin/bin/bash.exe";
cmmd[1] = "-c";
cmmd[2] = "cd /cygdrive/c/Users/pro-services/Desktop/projects/github/cygwin && bash TEST.sh";
Related
This question already has an answer here:
Why does Runtime.exec(String) work for some but not all commands?
(1 answer)
Closed 10 months ago.
I am doing an exercise related to Runtime.exec(), I understand that Runtime.exec is not a shell interpreter, that's why I execute "bash -c 'command'" instead, but for some reason, I can execute commands like ls bash -c 'ls' but not echo or redirection or multiple commands. These does not work:
bash -c 'echo 1234'
bash -c 'ls > abc'
bash -c 'ls;id'
bash -c 'ls -al'
Here is my java code:
import java.util.*;
import java.io.*;
public class runtime {
public static void main(String args[]) throws Exception{
String cmd = args[0];
System.out.println(cmd);
Process p = Runtime.getRuntime().exec(cmd);
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
System.out.println("Out: " + disr);
disr = dis.readLine();
}
}
}
I run the above commands with the syntax:
java runtime "bash -c 'command'"
This works:
$ java runtime "bash -c 'ls'"
bash -c 'ls'
Out: Main.class
Out: Main.java
Out: runtime.class
Out: runtime.java
I am using openjdk 11.0.15 on Ubuntu 20.04 and zsh.
Can anyone tell me why Runtime doesn't work in this case? Thank you!
Because of shell parsing.
These are all concepts that the OS just does not have:
The concept of 'every space separates one argument from another (and the command from the list of arguments'). The concept that a single string can possibly run anything, in fact; at the OS level that's just not what the 'run this thing' API looks like. When you type commands on the command prompt, your shell app is 'interpreting' these strings into a command execution request.
The concept of figuring out that bash means /bin/bash, i.e. $PATH resolution.
The concept that *.txt is supposed to refer to all files that ends in .txt.
The concept that $FOO should be replaced with the value of the environment variable 'FOO'
The concept that ; separates 2 commands, and it's supposed to run both.
The concept that single and double quotes escape things. "Escape things" implies that things can cause interpretation to happen. The OS interprets nothing, therefore there's nothing to escape. Obvious, then, that the OS doesn't know what ' is, or ".
That >foo means: Please set the standard output of the spawned process such that it sends it all to file 'foo'.
In windows shells, that # in front of the command means 'do not echo the command itself'. This and many other things are shellisms: /bin/bash does that, maybe /bin/zsh does something else. Windows's built in shell thing definitely is quite different from bash!
Instead, an OS simply wants you to provide it a full path to an executable along with a list of strings, and pick targets for standard in, standard out, and standard err. It does no processing on any of that, just starts the process you named, and passes it the strings verbatim.
You're sort of half there, as you already figured out that e.g. ls >foo cannot work if you execute it on its own, but it can work if you tell bash to execute it. As ALL of that stuff in the above list? Your shell does that.
It gets more complicated: Turning *.txt into foo.txt bar.txt is a task of bash and friends, e.g. if you attempted to run: ls '*.txt' it does not work. But on windows, it's not the shell's job; the shell just passes it verbatim to dir, and it is the command's job to undo it. What a mess, right? Executing things is hard!
So, what's wrong here? Two things:
Space splitting isn't working out.
Quote application isn't being done.
When you write:
bash -c 'ls >foo'
in a bash shell, bash has to first split this up, into a command, and a list of arguments. Bash does so as follows:
Command: bash
arg1: -c
arg2: ls >foo
It knows that ls >foo isn't 2 arguments because, effectively, "space" is the bash operator for: "... and here is the next argument", and with quotes (either single or double), the space becomes a literal space instead of the '... move to next argument ...' operator.
In your code, you ask bash to run java, and then java to run bash. So, bash first does:
cmd: java
arg1: bash -c 'ls >foo'
With the same logic at work. Your java app then takes that entire string (that's args[0]: "bash -c 'ls >foo'"), and you then feed it to a method you should never use: Runtime.exec(). Always use ProcessBuilder, and always use the list-based form of it.
Given that you're using the bad method, you're now asking java to do this splitting thing. After all, if you just tell the OS verbatim, please run "bash -c 'ls >foo'", the OS dutifully reports: "I'm sorry, but file ./bash -c ;ls >foo' does not exist", because it does not processing at all". This is unwieldy so java's exec() method is a disaster you should never use: Because people are confused about this, it tries to do some extremely basic processing, except every OS and shell is different, java does not know this, so it does a really bad job at it.
Hence, do not use it.
In this case, java doesn't realize that those quotes mean it shouldn't split, so java tells the OS:
Please run:
cmd: /bin/bash (java DOES do path lookup; but you should avoid this, do not use relative path names, you should always write them out in full)
arg1: -c
arg2: 'ls
arg3: >foo'
and now you understand why this is just going completely wrong.
Instead, you want java to tell the OS:
cmd: /bin/bash
arg1: -c
arg2: ls >foo
Note: ls >foo needs to be one argument, and NOTHING in the argument should be containing quotes, anywhere. The reason you write:
/bin/bash -c 'ls >foo'
In bash, is because you [A] want bash not to treat that space between ls and >foo as an arg separator (you want ls >foo to travel to /bin/bash as one argument), and [B] that you want >foo to just be sent to the bash you're spawning and not to be treated as 'please redirect the output to file foo' at the current shell level.
Runtime.exec isn't a shell, so the quotes stuff? Runtime.exec has no idea.
This means more generally your plan of "I shall write an app where the entire argument you pass to it is just run" is oversimplified and can never work unless you write an entire quotes and escaper analyser for it.
An easy way out is to take the input, write it out to a shell script on disk, set the exec flag on it, and always run /bin/bash -c /path/to/script-you-just-wrote, sidestepping any need to attempt to parse anything in java: Let bash do it.
The ONE weird bizarro thing I cannot explain, is that literally passing 'ls' to /bin/bash -c, with quotes intact, DOES work and runs ls as normal, but 'ls *' does not, perhaps because now bash thinks you want executable file /bin/ls * which obviously does not exist (a star cannot be in a file name, or at least, that's not the ls executable, and it's not an alias for the ls built-in). At any rate, you want to pass ls without the quotes.
Let's try it!
import java.util.*;
import java.io.*;
public class runtime {
public static void main(String args[]) throws Exception{
ProcessBuilder pb = new ProcessBuilder();
pb.command("/bin/bash", "-c", "echo 1234");
// pb.command("/bin/bash", "-c", "'echo 1234'");
Process p = pb.start();
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
System.out.println("Out: " + disr);
disr = dis.readLine();
}
int errCode = p.waitFor();
System.out.println("Process exit code: " + errCode);
}
}
The above works fine. Replace the .command line with the commented out variant and you'll notice it does not work at all, and you get an error. On my mac I get a '127' error; perhaps this is bash reporting back: I could not find the command you were attempting to execute. 0 is what you're looking for when you invoke waitFor: That's the code for 'no errors'.
Before you report this as a duplicate, understand that I have now spent a few hours looking over similar questions from a multitude of different websites, many being from here. They do not explain the solution well enough for me to take their answers and apply them to my own problem. If you still feel the itch to report this, go for it. All you'll be doing is preventing me from learning to code better.
I am attempting to call a python script and pass it 5 arguments. I have tried a few different ways to do this and believe the process builder route is my best option. However, I have a few questions as it does not seem to be the right code:
Do I need to be giving process builder a path to an executable, or can I just give it a path to the normal .py file?
Do I need to collect the output from the python file?
If there are any other aspects of the problem I am not seeing, please let me know. My code looks correct compared to others' on the internet doing the same thing. It is listed below:
private void runPython(String pythonPath, HashMap<String, String> map){
pythonPath = "C:/Users/Carlos/PycharmProjects/autoHTML/javaToExcel.py";
try {
ProcessBuilder pb = new ProcessBuilder(pythonPath + "/" + "python", pythonPath, map.get("Driver Advances"), map.get("Driver Loans"),
map.get("Escrow Fund"), map.get("Maintenance Fund"), map.get("Highway Use tax - 2290"));
Process p = pb.start();
catch(Exception e) {
System.out.println("Python error");
}
}
if there is any code you want or any questions you need answered to help me out, please let me know.
To create an operating system processes, you can use ProcessBuilder which takes 2 arguments:
The process to run, here this is the path to your Python executable (a.k.a: "C:/Users/Carlos/PycharmProjects/autoHTML/javaToExcel.py/python.exe" or something similar).
The arguments to pass to your process:
The path to your python script and its own arguments.
You can try with:
ProcessBuilder pb = new ProcessBuilder(
"C:/Users/Carlos/PycharmProjects/autoHTML/javaToExcel.py/python.exe",
pythonPath,
map.get("Driver Advances"),
map.get("Driver Loans"),
map.get("Escrow Fund"),
map.get("Maintenance Fund"),
map.get("Highway Use tax - 2290"));
In Java, I start one new Process using Runtime.exec(), and this process in turn spawns several child processes.
I want to be able to kill all the processes, and have previously been trying process.destroy() and process.destroyForcibly() - but the docs say that destroyForcibly() just calls destroy() in the default implementation and destroy() may not kill all subprocesses (I've tried and it clearly doesn't kill the child processes).
I'm now trying a different approach, looking up the PID of the parent process using the method suggested here and then calling ps repeatedly to traverse the PIDs of child processes, then killing them all using kill. (It only needs to run on Linux).
I've managed the first bit - looking up the PID, and am trying the following command to call ps to get the child PIDs:
String command = "/bin/ps --ppid " + pid;
Process process = new ProcessBuilder(command).start();
process.waitFor();
Unfortunately the 2nd line above is throwing an IOException, with the following message: java.io.IOException: Cannot run program "/bin/ps --ppid 21886": error=2, No such file or directory
The command runs fine if I paste it straight into the terminal on Ubuntu 16.04.
Any ideas would be very much appreciated.
Thanks
Calling the command you wish to run this way is always destined to fail.
Since Process does not effectively run a shell session, the command is basically handed over to the underlying OS to run. This means that it'll fail, since the path to t he program to be executed (in this case ps), is not the full one hence the error you're getting.
Also, testing whether your command works using a terminal is not correct. Using a terminal contains the notion of performing an action with an active logged in user with a correct path etc etc. All the above are not the case though when running a command through Process as these are not taken into consideration.
Furthermore, you also need to account for cases where the actual java application could be running under a different user, with a different set of permissions, paths etc.
In order for your to fix this, you can simply do either of the following:
1) Invoke your ps command using the full path to it (still not sure if it would work)
2) Change the way your create the Process object into something like: p = new ProcessBuilder("bash", "-c", command).start();
The second, will effectively run a bash session, passing in the ps command as an argument thus obtaining the desired result.
http://commons.apache.org/proper/commons-exec/tutorial.html
```
String line = "AcroRd32.exe /p /h " + file.getAbsolutePath();
CommandLine cmdLine = CommandLine.parse(line);
DefaultExecutor executor = new DefaultExecutor();
int exitValue = executor.execute(cmdLine);
```
I am trying to write a program that calls external jars from the command line. In my code it will do java -jar test,jar args. What I want to know though is if a error occurs in this external jar, how to catch it in my java program so I can do the necessary procedure? This is a new zone of coding for me from college level so I am a little clueless.
Command-line programs returns exit status when finished executing it's work (e.g. zero when everything is ok).
You should be able to retrieve something interesting by storing the return value of your system call and test it according to what you want to do.
// Code from https://stackoverflow.com/questions/8496494/
Runtime rt = Runtime.getRuntime();
Process pr = rt.exec("java -jar test.jar args");
// Check retVal to test
int retVal = pr.waitFor();
More about this in this SO question.
It's not the first time I have tried to execute a system command from Java; but this time it turns out to be very hard. I have a script that executes just fine from the terminal. It reads input from a file (input.txt), it processes it and exports the result in another file (ouput.txt). The whole thing lasts no more than 1sec. But, when I try to execute it from Java, it gets stuck and never finishes. This is my code:
Process p = new ProcessBuilder("./runCalculator.sh").start();
p.waitFor();
I have also tried with Runtime.getRuntime().exec("./runCalculator.sh") but all the same. I've read both the InputStream and the ErrorStream of the process. The error stream returns nothing but a message like "Starting Calculation..."
Any ideas?
You need to use the following code:
ProcessBuilder pb = new ProcessBuilder();
pb.command("bash", "-c", "./runCalculator.sh");
Process process = pb.start();
int retValue = process.waitFor();
You likely need to invoke the unix command interpreter/processor for this to work. Please see: When Runtime.exec() won't.
Try this:
Process p = new ProcessBuilder("sh ./runCalculator.sh").start();
Another, simplier solution is that you can open program by entering the name of the program (this assumes that program is installed) instead of creating script and calling it.
Note that the name of the program isn't always what you see in Gnome's menu, for example Gnome's calculator is "gnome-calculator". Regarding this facts, you can run calculator by the folowing line:
Process p = Runtime.getRuntime().exec("gnome-calculator");
In that case you don't have a need for any sh scripts (in your case runCalculator.sh).