I have a Windows batch file simple.bat which produces some output in stdin, and ends with a pause. I want to run this batch file and then process the output in my Java code.
// create a Java process with simple.bat
Process p = Runtime.getRuntime().exec("cmd /c simple.bat");
// get the output from the process p
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder builder = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
builder.append(line);
builder.append(System.getProperty("line.separator"));
}
String result = builder.toString();
The problem with the above code is that simple.bat ends with a pause and that makes p still hanging in there waiting for the key to be pressed. As a result, eventually reader.readLine() will be blocked and never returns.
Unfortunately, simple.bat is passed on to me as-is. I cannot remove the pause line.
I can add
Thread.sleep(1000);
p.destroy();
after the exec("cmd /c start simple.bat") line to terminate the process p before processing its output. What if the batch file is run on slow machine? I am seeking advice is there a better way then asynchronously stopping the process. Thank you very much!!
If your script doesn't require any input sent to it, and you are running it as cmd /c simple.bat, then the simplest thing to do is to close the process's standard input:
p.getOutputStream().close();
I ran a quick test myself, and this dismissed the pause and let the script terminate. You will of course have the extra line Press any key to continue . . . in your output, but I imagine you can deal with that.
Destroying the process can be correct as a fallback. You will have to think about how you would prefer your application to fail: is aborting to soon worse than a hanging process? Which of the failures will the user actually notice?
In most cases both of the above are undesirable.
If you know you have reached the pause (based on the last readline()), you can actually write to the process. This can be done by getting an output stream Process.getOutputStream(). I haven't been able to test this, but normally pause should also react to characters written this stream.
Related
I have this code
private static void restartTor() throws IOException, InterruptedException {
String killTor = "killall tor";
String startTor = "/opt/local/bin/tor -f /dev/torrc";
Runtime run = Runtime.getRuntime();
Process pr = run.exec(killTor);
pr.waitFor();
BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream()));
String line = "";
while ((line=buf.readLine())!=null) {
System.out.println(line);
}
pr = run.exec(startTor);
pr.waitFor();
buf = new BufferedReader(new InputStreamReader(pr.getInputStream()));
line = "";
while ((line=buf.readLine())!=null) {
System.out.println(line);
}
}
When I run this on computer A it executes as expected but when I run it on my second computer, B, it gets stuck at the second pr.waitFor();.
I have read a bunch of questions here on SE, such as process.waitFor() never returns and Java process.waitFor() does not return and the main issue there seems to be that you don't read the buffer but I do that (don't I?).
A and B are similar, but not identical (Macs, running 10.15, A has 32 GB RAM, B has 16 GB RAM).
I use the same version of tor and the torrc:s are identical on A and B.
I am stumped. What is the problem here?
Edit: On B, If I manually, from a regular terminal, kill the process, it returns and everything continues as expected.
Edit 2: Now it fails on computer A as well. I had run it dozens of times there, without problems before but now it fails constantly.
I don't know if this this is the ultimate cause of your problem, but you should call waitFor after reading the output (and errors) from the external process. If a process writes more to its standard output and error streams than the OS is prepared to buffer, then your code could deadlock:
The external process is blocked while trying to write output
Your Java code is blocked waiting for the external process to exit.
You need to consume both streams at same time if you are experiencing the stream deadlock issue on waitFor(). You can do this with background threads on pr.getErrorStream(), or set up STDERR handling before calling waitFor().
To do this replace use of Runtime.exec with ProcessBuilder.
ProcessBuilder pb = new ProcessBuilder(startTor);
This is easier with either sending error log to a file:
File stderr = new File("stderr.log");
pb.redirectError(stderr));
... or just redirect error to merge it with stdout:
pb.redirectErrorStream(true);
then
pr = pb.start();
pr.waitFor();
In my program I have a SwingWorker starting a background process. The background process's error stream is redirected to stdout, and the stdout stream is written out (line by line) to a JTextArea. I thought I was consuming the stdout stream with this
BufferedReader processOut = new BufferedReader(
new InputStreamReader(p.getInputStream()));
And with this inside the SwingWorker's doInBackground:
String line;
while((line = processOut.readLine()) != null)
process(line);
On my machine, the process executes to completion and the Text Area is periodically updated. The process freezes in the background on other computers though. I've increased the size of the default command window, so that might be why I can't get any process to freeze on my computer (that is probably very wrong from the more that I read).
I tried redirecting the output inside the ProcessBuilder command with > log.txt (I'm on Windows 7 currently) but I think that's causing the p.getInputStream() call to crash.
How can I either consume the stdout of the subprocess properly inside my SwingWorker class, or is it possible to pipe the output to a file and still get the output to print to the JTextArea.
Edit:
I read here That an input stream needs to be read promptly to be consumed. I've provided the loop that processes the input stream below. I would assume that the pipe doesn't get more than 4K of data before it is read, but I can't assume anything at this point.
while(processIsAlive())
{
if(isCancelled())
{
try
{
p.destroy();
p.waitFor();
return 1;
}
catch(Exception e){}
}
try
{
//Update Text Area
//get output from process
while((line = processOut.readLine()) != null)
{
//print output to text area
publish(line);
}
sleep(1000);
}
catch(Exception e){}
}
EDIT 2:
I thought that having the process redirected to a file, then putting an InputStream on that same file would be impossible, but its not crashing my program and the GUI is still updated properly. I'm going to go test this on the problematic machine, then I'm going to mark it as an answer if it works.
All I did was redirect the process to a file:
pb.redirectOutput(new File("log.txt"));
And put the InputStream on that file instead of the process stdout stream.
processOut = new BufferedReader(new FileReader("log.txt"));
When starting processes, it's convenient to read the stdout and stderr in a separate thread each one, because reading must be done through a call to InputStream.read(), which does block the owner thread.
You said that your background process was redirecting errors to stdout, ok. Then, read only the stdout, but do it always in a separate thread, because the main thread gets blocked in the waitFor call until the process ends. But the process will block if its output buffer gets full and nobody is reading it.
In this case, the difference between your local environment (success) and the others (failing) could be that, in yours, the amount of data fits in the output buffer.
My second edit will work on any machine with Java 7 or later. The reading from the input stream is blocking, so that is incorrect for using in a GUI but the process streams are read from appropriately.
The actual problem with the machine was that for some reason the program wasn't flushing appropriately, so I added fflush(stdout) to the c source code in the problematic program, and it worked perfectly.
How to get the output of the command prompt which means i have opend a command prompt like this.
Process process = Runtime.getRuntime().exec("cmd /c start cmd.exe /K \"C:\\Editor\\editorTemp.exe\"");
i can not get the cmd output like this
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
So how can i get the command prompt output ?
This is not Java question. Basically what you doing is running Java (Java Main Process A) and from it starting another process (Windows CMD B). This is fine and you can get input/output streams of this process (B) in Java(A).
However this process (B) starts another process (again Windows CMD C) with its own standard input/output. This process has nothing common with processes A&B and uses Windows' standard Input/Output streams. So, there are no connections between A and C.
I'm not sure but I think there are some ways to run Windows CMD with different or not standard IO. Maybe something like this will work:
cmd <tty >tty
but there is no tty in Windows. Pragmatically you can do this as described here - Creating a Child Process with Redirected Input and Output but that would not work for regular CMD.
Nevertheless it became even more problematic when you start your own process from the editorTemp.exe (process D). D has even more disconnection with process A. And all for what? What don't you simply start process D directly from A and have full control on the IO streams and process itself?
Here is good example how to do so.
Your java thread is working independently of CMD call. The java code is beating the STDOUT pipe before anything is written.
If you call Process.waitFor(), it will wait until the CMD call is done. The STDOUT should be in the buffer, and then you can read it.
When you do a readLine(), your java thread is blocked until you have an actual full line or the input stream is closed.
If the program prints a partial line (without CR or LF at the end), and then waits for input, the readLine will be stuck.
So you will need to read character by character, until you think the proces has no more things to say.
See e.g. Is it possible to read from a InputStream with a timeout?
import java.util.Scanner;
Inside the main write this.
Scanner output = new Scanner(System.in);
System.out.println(“Enter your name”);
String name = output.next();
If you want user to enter an int then you need to do this.
int number = output.nextInt();
I tested this code(below) on several different linux boxes(4+) and it worked fine. However, on one linux box I ran into an issue with readline() hanging for the error inputStream(errorStream). This stream should be empty so I suspected that box was not writing out a line terminator to the errorStream for the error. I changed my code to use read() instead of readline()...but read() also hung.
I tried retrieving the input inputStream first, and that worked and there was no hangs with readline()/read() for the error inputstream. I could not do this since I needed to obtain possible errors first. Appearing to be a deadlock, I was able to resolve this by having each inputstream read from it's own thread. Why did I only see this issue on one box? Is there a kernel setting or some other setting specific to this box that could have caused this?
ProcessBuilder processBuilder = new ProcessBuilder()
try
{
Process processA = null;
synchronized (processBuilder)
{
processBuilder.command("/bin/sh","-c"," . /Home/SomeScript.ksh");
processA = processBuilder.start();
}
inputStream = processA.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream));
errorStream = processA.getErrorStream();
errorReader = new BufferedReader(new InputStreamReader(errorStream));
String driverError;
while ((driverError = errorReader.readLine()) != null)
{
//some code
}
Why did I only see this issue on one box?
Most likely because of something in the script that is being run ... and its interactions with its environment (e.g. files, environment variables, etc)
Is there a kernel setting or some other setting specific to this box that could have caused this?
It is possible but unlikely that it is a kernel setting. It might be "something else". Indeed, it has to be "something" outside of the Java application that is to blame, at least in part.
I suggest you do the following temporarily (at least):
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("/bin/sh","-c"," . /Home/SomeScript.ksh");
processBuilder.redirectErrorStream(true);
processA = processBuilder.start();
inputStream = processA.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
System.out.println("Return code is " + processA.exitValue());
That way you can see what all of the output is.
There should not be a problem if the external process fails to put a newline at the end of the last line. The Java process will see an EOF on the input stream, and the BufferedReader will return what characters it has ... and return null on the next call.
Another possibility is that the external process is blocking because it is trying to read from its standard input.
UPDATE
The redirectErrorStream also resolved the issue, but I need the error stream separate.
OK so if it did (reliably) solve the problem then that (most likely) means that you have to read the external processes stdout and stderr streams in parallel. The simple way to do is to create 2 threads to read and buffer the two streams separately. For example: Capturing stdout when calling Runtime.exec
(Your problem is due to the fact that pipes have a finite buffering capacity. The external problem is most likely alternating between writing stuff to stdout and stderr. If it tries to write to one of the pipes when that pipe is "full", it will block. But if your application is reading all of the other pipe (to EOF) before it reads the blocked pipe, then everything will deadlock. The fact that the external process is stuck in PIPE_W state is more evidence for this explanation.
One possible reason that you are seeing different behaviour on different systems is that the amount of buffering in a pipe is system dependent. But it could also be due to differences in what the external process is doing; e.g. its inputs.)
You are running OS specific commands in a script, any one could be holding the error output. You can avoid this by discarding the errors, but that is unlikely to be a good idea.
I would check the version of the OS are the same and whether you have any significant differences in the command you run in the script. If this doesn't help, take out commands from the script until it starts working. I assume an empty script doesn't do this.
I have written a small application for a project..that will do following tasks:
Writes a commands.bat file. This bat file has some source-code-server commands that will take sometime to get process.
Executes the commands.bat using ProcessBuilder and get outputfile.txt using redirectOutput(File file) method.
Reads the outputfile.txt and get the desired output.
When I run this application, the program control starts with step-1 and executes it completely. In step-2 the control starts a process that drives the batch file. Now commands.bat file takes some time to finish (depends on the response from source code server). Sometimes this batch takes a little more than the reasonable time, for which the control is not waiting and starts executing step-3, and this way I am not getting the complete stream in the outfile.txt.
I also used things like:
waitfor(): Even with this control is not waiting for process to end(technically I may be wrong)
Thread.sleep(). This is not working as time taken in batch file processing is not certain.
Please help.
This is how I am waiting for a batch file to execute. Hopefully you have solved the problem by now. But, it might help someone else who looks at this question
// Any command you want to run in my case im executing a batch file
String cmd = "load_execute.bat";
//FILE_PATH is the directory where to starting from
ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", cmd).redirectErrorStream(true);
builder.directory(new File(FILE_PATH));
Process process = builder.start();
//Redirect stream from cmd stream to local print stream
BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = input.readLine()) != null) {
System.out.println(line);
}
input.close();
res = process.waitFor();`