lmBuilder.redirectErrorStream();
This is incorrect.
This method only tells whether you redirect stderr to stdout; it does not instruct that stderr should be redirected to it.
What you should use is:
lmBuilder.redirectErrorStream(true);
Answer from fge on Stack Overflowjava -version writes to stderr, so you need pb.redirectErrorStream(true); to capture the output.
ProcessBuilder pb = new ProcessBuilder("C:\\myPrograms\\jdk1.7.0_79\\bin\\java.exe", "-version");
pb.redirectErrorStream(true);
...
private static class IOThreadHandler extends Thread {
private InputStream inputStream;
private StringBuilder output = new StringBuilder();
IOThreadHandler(InputStream inputStream) {
this.inputStream = inputStream;
}
public void run() {
try (Scanner br = new Scanner(new InputStreamReader(inputStream))) {
String line = null;
while (br.hasNextLine()) {
line = br.nextLine();
output.append(line).append(System.getProperty("line.separator"));
}
}
}
public String getOutput() {
return output.toString();
}
}
The problem I see here is that you create a process running a shell (OK), get hold of the input and output streams of that process (OK), read a command from the file (OK) and feed it to the process (OK). Then you keep reading output lines, which succeeds while the the first Java program executes and produces output.
Then the
while ((output = processOutput.readLine()) != null) { ...
blocks as there is neither another line nor EOF.
You can fix this by spawning a thread to read and print processOutput.
Another option (which I'd very much prefer) is to create one process per command. As far as I can see you don't even need a shell: you could execute java SolveProblem ... right away. (Unices have been built for efficient subprocess creation, and don't think the shell does it differently, so there's no additional overhead to be afraid of.)
Just two hints for calling java without a shell: make sure to use the full path name and split the command line into tokens.
Edit And here it is, just using a String[] instead of your text file containing commands.
for( String cmd: new String[]{ "java Y aaa", "java Y bbb","java Y ccc" } ){
String[] toks = cmd.split( "\\s" );
ProcessBuilder builder = new ProcessBuilder( toks );
Process p = builder.start();
// get output from the process
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader processOutput = new BufferedReader(isr);
InputStream errorStream = p.getErrorStream();
InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
BufferedReader processErrorOutput = new BufferedReader(inputStreamReader);
System.out.println("Executing " + cmd);
String output;
while( processErrorOutput.ready() &&
(output = processErrorOutput.readLine()) != null) {
System.out.println(output);
}
while ((output = processOutput.readLine()) != null) {
System.out.println(output);
}
processErrorOutput.close();
processOutput.close();
}
Output (silly Y prints argument three times):
Executing java Y aaa 1aaa 2aaa 3aaa Executing java Y bbb 1bbb 2bbb 3bbb Executing java Y ccc 1ccc 2ccc 3ccc
Another Edit If a marker line is inserted into the process output and the read loop checks the lines for this marker, the program can be used as it is (except for some corrections for closing processInput):
processInput.write(command + "; echo xxxEOFxxx");
//...
while ((output = processOutput.readLine()) != null
&& ! "xxxEOFxxx".equals(output)) {
System.out.println(output);
}
Although I dislike the use of "magic strings" in this way it may be permissible here as you know the set of output lines of program Solve.
And yet better is to run error and stream reading in separate threads in order to provide make program able to do something else, terminate process for instance
The method ProcessBuilder.inheritIO will redirect your command streams in your stdin, stdout and stderr. This applies to Java 7.
String[] args = {
"java",
"-jar",
"C:\\Program Files\\Java\\lib\\enchanter-beanshell-0.6.jar"
};
ProcessBuilder builder = new ProcessBuilder(args);
Start with breaking up the arguments as above. Then implement all the recommendations of When Runtime.exec() won't.
The DISCARD redirect option has been added in Java 9. You could use that if you upgrade Java. Otherwise, you could simply replicate that behaviour because the DISCARD redirect enum uses a file instance which redirects to null device as below.
private static File NULL_FILE = new File(
(System.getProperty("os.name")
.startsWith("Windows") ? "NUL" : "/dev/null")
);
Then you could use the overloaded redirectOutput method;
if (suppressOutput) {
processBuilder.redirectOutput(NULL_FILE);
}
This is identical to Java 9's behaviour.
The Java 9+ solution for discarding the output of the Process:
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectOutput(Redirect.DISCARD);
processBuilder.redirectError(Redirect.DISCARD);
Process process = processBuilder.start();
I have never used gksudo, but I googled it and it says it's a GUI version of sudo. I'm guessing that you just launched a GUI app which does not write anything to stdout and which does not return. If so, then the code is doing what I would expect. It is blocking until the process writes a line of text that it can read - which never occurs so it blocks indefinitely.
First test your ProcessBuilder code using a trivial command like "echo" to make sure your Java code is working as expected. Then work your way back. Try running your program as root so you don't need the sudo argument and see if that works. Then finally try to run it using sudo instead of gksudo.
I think @user is on the right track, but there are a couple of other possible explanations.
The gksudo command could be asking for a password. I'm not sure where it would ask, but there's a good chance that it won't be the "stdout" stream of the "gksudo" process.
If "gksudo" or the command that you are "gksudo"-ing fails to launch, there is a good chance that it will write an error message to its "stderr" stream. But you are not reading "stderr".
To help diagnose this, you need to try the following:
- Look in the log file that for "sudo" - it is "/var/log/secure" on my box.
- Use "ps -efl" (or similar) to see what processes exist while your application is blocked waiting for output. (If that is happening ...)
- Look to see if "gksudo" is prompting for a password in an unexpected place.
- Try temporarily tweaking the "sudoers" file to allow the "arpspoof" command to be "sudo"-ed without a password.
If the process writes to stderr or stdout, and you're not reading it - it will just "hang" , blocking when writing to stdout/err. Either redirect stdout/err to /dev/null using a shell or merge stdout/err with redirectErrorStream(true) and spawn another thread that reads from stdout of the process
You want the trick?
Don't start your process from ProcessBuilder.start(). Don't try to mess with stream redirection/consumption from Java (especially if you give no s**t about it ; )
Use ProcessBuilder.start() to start a little shell script that gobbles all the input/output streams.
Something like that:
#!/bin/bash
nohup $1 >/dev/null 2>error.log &
That is: if you don't care about stdout and still want to log stderr (do you?) to a file (error.log here).
If you don't even care about stderr, just redirect it to stdout:
#!/bin/bash
nohup $1 >/dev/null 2>1 &
And you call that tiny script from Java, giving it as an argument the name of the process you want to run.
If a process running on Linux that is redirecting both stdout and stderr to /dev/null still produce anything then you've got a broken, non-compliant, Linux install ;)
In other word: the above Just Works [TM] and get rid of the problematic "you need to consume the streams in this and that order bla bla bla Java-specific non-sense".