Java Process With Input/Output Stream

Java Process with Input/Output Stream

Firstly, I would recommend replacing the line

Process process = Runtime.getRuntime ().exec ("/bin/bash");

with the lines

ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();

ProcessBuilder is new in Java 5 and makes running external processes easier. In my opinion, its most significant improvement over Runtime.getRuntime().exec() is that it allows you to redirect the standard error of the child process into its standard output. This means you only have one InputStream to read from. Before this, you needed to have two separate Threads, one reading from stdout and one reading from stderr, to avoid the standard error buffer filling while the standard output buffer was empty (causing the child process to hang), or vice versa.

Next, the loops (of which you have two)

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

only exit when the reader, which reads from the process's standard output, returns end-of-file. This only happens when the bash process exits. It will not return end-of-file if there happens at present to be no more output from the process. Instead, it will wait for the next line of output from the process and not return until it has this next line.

Since you're sending two lines of input to the process before reaching this loop, the first of these two loops will hang if the process hasn't exited after these two lines of input. It will sit there waiting for another line to be read, but there will never be another line for it to read.

I compiled your source code (I'm on Windows at the moment, so I replaced /bin/bash with cmd.exe, but the principles should be the same), and I found that:

  • after typing in two lines, the output from the first two commands appears, but then the program hangs,
  • if I type in, say, echo test, and then exit, the program makes it out of the first loop since the cmd.exe process has exited. The program then asks for another line of input (which gets ignored), skips straight over the second loop since the child process has already exited, and then exits itself.
  • if I type in exit and then echo test, I get an IOException complaining about a pipe being closed. This is to be expected - the first line of input caused the process to exit, and there's nowhere to send the second line.

I have seen a trick that does something similar to what you seem to want, in a program I used to work on. This program kept around a number of shells, ran commands in them and read the output from these commands. The trick used was to always write out a 'magic' line that marks the end of the shell command's output, and use that to determine when the output from the command sent to the shell had finished.

I took your code and I replaced everything after the line that assigns to writer with the following loop:

while (scan.hasNext()) {
String input = scan.nextLine();
if (input.trim().equals("exit")) {
// Putting 'exit' amongst the echo --EOF--s below doesn't work.
writer.write("exit\n");
} else {
writer.write("((" + input + ") && echo --EOF--) || echo --EOF--\n");
}
writer.flush();

line = reader.readLine();
while (line != null && ! line.trim().equals("--EOF--")) {
System.out.println ("Stdout: " + line);
line = reader.readLine();
}
if (line == null) {
break;
}
}

After doing this, I could reliably run a few commands and have the output from each come back to me individually.

The two echo --EOF-- commands in the line sent to the shell are there to ensure that output from the command is terminated with --EOF-- even in the result of an error from the command.

Of course, this approach has its limitations. These limitations include:

  • if I enter a command that waits for user input (e.g. another shell), the program appears to hang,
  • it assumes that each process run by the shell ends its output with a newline,
  • it gets a bit confused if the command being run by the shell happens to write out a line --EOF--.
  • bash reports a syntax error and exits if you enter some text with an unmatched ).

These points might not matter to you if whatever it is you're thinking of running as a scheduled task is going to be restricted to a command or a small set of commands which will never behave in such pathological ways.

EDIT: improve exit handling and other minor changes following running this on Linux.

Java Process with concurrent Input/Output Streams

You are on the right way with your code. There are only some minor things you missed.

Lets start with your read method:

private void read(String command){
[...]
// Write to Process
if (outStream != null) {
[...]
try {
writer.write(command + "\n"); // add newline so your input will get proceed
writer.flush(); // flush your input to your process
} catch (IOException e1) {
e1.printStackTrace();
}
}
// ELSE!! - if no outputstream is available
// Execute Command
else {
try {
exec(command);
} catch (IOException e) {
// Handle the exception here. Mostly this means
// that the command could not get executed
// because command was not found.
println("Command not found: " + command);
}
}
inPane.setText("");
}

Now lets fix your exec method. You should use separate threads for reading normal process output and error output. Additionally I introduce a third thread that waits for the process to end and closes the outputStream so next user input is not meant for process but is a new command.

private void exec(String command) throws IOException{
Process pro = Runtime.getRuntime().exec(command, null);

inStream = pro.getInputStream();
inErrStream = pro.getErrorStream();
outStream = pro.getOutputStream();

// Thread that reads process output
Thread outStreamReader = new Thread(new Runnable() {
public void run() {
try {
String line = null;
BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
while ((line = in.readLine()) != null) {
println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Exit reading process output");
}
});
outStreamReader.start();

// Thread that reads process error output
Thread errStreamReader = new Thread(new Runnable() {
public void run() {
try {
String line = null;
BufferedReader inErr = new BufferedReader(new InputStreamReader(inErrStream));
while ((line = inErr.readLine()) != null) {
println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Exit reading error stream");
}
});
errStreamReader.start();

// Thread that waits for process to end
Thread exitWaiter = new Thread(new Runnable() {
public void run() {
try {
int retValue = pro.waitFor();
println("Command exit with return value " + retValue);
// close outStream
outStream.close();
outStream = null;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
exitWaiter.start();
}

Now this should work.

If you enter ipconfig it prints the command output, closes the output stream and is ready for a new command.

If you enter cmd it prints the output and let you enter more cmd commands like dir or cd and so on until you enter exit. Then it closes the output stream and is ready for a new command.



You may run into problems with executing python scripts because there are problems with reading Process InputStreams with Java if they are not flushed into system pipeline.

See this example python script

print "Input something!"
str = raw_input()
print "Received input is : ", str

You could run this with your Java programm and also enter the input but you will not see the script output until the script is finished.

The only fix I could find is to manually flush the output in the script.

import sys
print "Input something!"
sys.stdout.flush()
str = raw_input()
print "Received input is : ", str
sys.stdout.flush()

Running this script will bahave as you expect.


You can read more about this problem at

  • Java: is there a way to run a system command and print the output during execution?
  • Why does reading from Process' InputStream block altough data is available
  • Java: can't get stdout data from Process unless its manually flushed

EDIT: I have just found another very easy solution for the stdout.flush() problem with Python Scripts. Start them with python -u script.py and you don't need to flush manually. This should solve your problem.

EDIT2: We discussed in the comments that with this solution output and error Stream will be mixed up since they run in different threads. The problem here is that we cannot distinguish if output writing is finish when error stream thread comes up. Otherwise classic thread scheduling with locks could handle this situation. But we have a continuous stream until process is finished no matter if data flows or not. So we need a mechanism here that logs how much time has elapsed since last line was read from each stream.


For this I will introduce a class that gets an InputStream and starts a Thread for reading the incoming data. This Thread stores each line in a Queue and stops when end of stream arrives. Additionally it holds the time when last line was read and added to Queue.

public class InputStreamLineBuffer{
private InputStream inputStream;
private ConcurrentLinkedQueue<String> lines;
private long lastTimeModified;
private Thread inputCatcher;
private boolean isAlive;

public InputStreamLineBuffer(InputStream is){
inputStream = is;
lines = new ConcurrentLinkedQueue<String>();
lastTimeModified = System.currentTimeMillis();
isAlive = false;
inputCatcher = new Thread(new Runnable(){
@Override
public void run() {
StringBuilder sb = new StringBuilder(100);
int b;
try{
while ((b = inputStream.read()) != -1){
// read one char
if((char)b == '\n'){
// new Line -> add to queue
lines.offer(sb.toString());
sb.setLength(0); // reset StringBuilder
lastTimeModified = System.currentTimeMillis();
}
else sb.append((char)b); // append char to stringbuilder
}
} catch (IOException e){
e.printStackTrace();
} finally {
isAlive = false;
}
}});
}
// is the input reader thread alive
public boolean isAlive(){
return isAlive;
}
// start the input reader thread
public void start(){
isAlive = true;
inputCatcher.start();
}
// has Queue some lines
public boolean hasNext(){
return lines.size() > 0;
}
// get next line from Queue
public String getNext(){
return lines.poll();
}
// how much time has elapsed since last line was read
public long timeElapsed(){
return (System.currentTimeMillis() - lastTimeModified);
}
}

With this class we could combine the output and error reading thread into one. That lives while the input reading buffer threads live and have not comsumed data. In each run it checks if some time has passed since last output was read and if so it prints all unprinted lines at a stroke. The same with the error output. Then it sleeps for some millis for not wasting cpu time.

private void exec(String command) throws IOException{
Process pro = Runtime.getRuntime().exec(command, null);

inStream = pro.getInputStream();
inErrStream = pro.getErrorStream();
outStream = pro.getOutputStream();

InputStreamLineBuffer outBuff = new InputStreamLineBuffer(inStream);
InputStreamLineBuffer errBuff = new InputStreamLineBuffer(inErrStream);

Thread streamReader = new Thread(new Runnable() {
public void run() {
// start the input reader buffer threads
outBuff.start();
errBuff.start();

// while an input reader buffer thread is alive
// or there are unconsumed data left
while(outBuff.isAlive() || outBuff.hasNext() ||
errBuff.isAlive() || errBuff.hasNext()){

// get the normal output if at least 50 millis have passed
if(outBuff.timeElapsed() > 50)
while(outBuff.hasNext())
println(outBuff.getNext());
// get the error output if at least 50 millis have passed
if(errBuff.timeElapsed() > 50)
while(errBuff.hasNext())
println(errBuff.getNext());
// sleep a bit bofore next run
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Finish reading error and output stream");
}
});
streamReader.start();

// remove outStreamReader and errStreamReader Thread
[...]
}

Maybe this is not a perfect solution but it should handle the situation here.


EDIT (31.8.2016)

We discussed in comments that there is still a problem with the code while implementing a stop button that kills the started
process using Process#destroy(). A process that produces very much output e.g. in an infinite loop will
be destroyed immediately by calling destroy(). But since it has already produced a lot of output that has to be consumed
by our streamReader we can't get back to normal programm behaviour.

So we need some small changes here:


We will introduce a destroy() method to the InputStreamLineBuffer that stops the output reading and clears the queue.

The changes will look like this:

public class InputStreamLineBuffer{
private boolean emergencyBrake = false;
[...]
public InputStreamLineBuffer(InputStream is){
[...]
while ((b = inputStream.read()) != -1 && !emergencyBrake){
[...]
}
}
[...]

// exits immediately and clears line buffer
public void destroy(){
emergencyBrake = true;
lines.clear();
}
}

And some little changes in the main programm

public class ExeConsole extends JFrame{
[...]
// The line buffers must be declared outside the method
InputStreamLineBuffer outBuff, errBuff;
public ExeConsole{
[...]
btnStop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(pro != null){
pro.destroy();
outBuff.destroy();
errBuff.destroy();
}
}});
}
[...]
private void exec(String command) throws IOException{
[...]
//InputStreamLineBuffer outBuff = new InputStreamLineBuffer(inStream);
//InputStreamLineBuffer errBuff = new InputStreamLineBuffer(inErrStream);
outBuff = new InputStreamLineBuffer(inStream);
errBuff = new InputStreamLineBuffer(inErrStream);
[...]
}
}

Now it should be able to destroy even some output spamming processes.


Note: I found out that Process#destroy() is not able to destroy child processes. So if you start cmd on windows
and start a java programm from there you will end up destroying the cmd process while the java programm is still running.
You will see it in the task manager. This problem could not be solved with java itself. it will need
some os depending external tools to get the pids of these processes and kill them manually.

Java - Use Input and OutputStream of ProcessBuilder continuously

You are probably experiencing a race condition: after writing the command to the shell, your Java program continues to run, and almost immediately calls reader.ready(). The command you wanted to execute has probably not yet output anything, so the reader has no data available. An alternative explanation would be that the command does not write anything to stdout, but only to stderr (or the shell, maybe it has failed to start the command?). You are however not reading from stderr in practice.

To properly handle output and error streams, you cannot check reader.ready() but need to call readLine() (which waits until data is available) in a loop. With your code, even if the program would come to that point, you would read only exactly one line from the output. If the program would output more than one line, this data would get interpreted as the output of the next command. The typical solution is to read in a loop until readLine() returns null, but this does not work here because this would mean your program would wait in this loop until the shell terminates (which would never happen, so it would just hang infinitely).
Fixing this would be pretty much impossible, if you do not know exactly how many lines each command will write to stdout and stderr.

However, your complicated approach of using a shell and sending commands to it is probably completely unnecessary. Starting a command from within your Java program and from within the shell is equally fast, and much easier to write. Similarly, there is no performance difference between Runtime.exec() and ProcessBuilder (the former just calls the latter), you only need ProcessBuilder if you need its advanced features.

If you are experiencing performance problems when calling external programs, you should find out where they are exactly and try to solve them, but not with this approach. For example, normally one starts a thread for reading from both the output and the error stream (if you do not start separate threads and the command produces large output, everything might hang). This could be slow, so you could use a thread pool to avoid repeated spawning of processes.

Java process swing InputStream and OutputStream operations

    System.out.println( "hello" );
String line;

Process process = Runtime.getRuntime ().exec ("cmd.exe /c slcanterm.exe");

InputStream stderr = process.getErrorStream ();
InputStream stdout = process.getInputStream ();

BufferedReader reader = new BufferedReader (new InputStreamReader(stdout));
BufferedReader error = new BufferedReader (new InputStreamReader(stderr));

new Thread() {
public void run() {
try {
String err;
while ((err = error.readLine ()) != null)
System.out.println ("[Stderr] " + err);
} catch (Exception e) {
e.printStackTrace();
}

}}.start();

while ((line = reader.readLine ()) != null)
System.out.println ("[Stdout] " + line);

System.out.println( "buy" );

Writing to InputStream of a Java Process

The Process OutputStream (our point of view) is the STDIN from the process point of view

OutputStream stdin = process.getOutputStream(); // write to this

So what you have should be correct.

My driver (apply your own best practices with try-with-resources statements)

public class ProcessWriter {
public static void main(String[] args) throws Exception {
ProcessBuilder builder = new ProcessBuilder("java", "Test");
builder.directory(new File("C:\\Users\\sotirios.delimanolis\\Downloads"));
Process process = builder.start();

OutputStream stdin = process.getOutputStream(); // <- Eh?
InputStream stdout = process.getInputStream();

BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

writer.write("Sup buddy");
writer.flush();
writer.close();

Scanner scanner = new Scanner(stdout);
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
}
}

My application

public class Test {

public static void main(String[] args) throws Exception {
Scanner console = new Scanner(System.in);
System.out.println("heello World");
while(console.hasNextLine()) {
System.out.println(console.nextLine());
}
}
}

Running the driver prints

heello World
Sup buddy

For some reason I need the close(). The flush() alone won't do it.

Edit It also works if instead of the close() you provide a \n.

So with

writer.write("Sup buddy");
writer.write("\n");
writer.write("this is more\n");
writer.flush();

the driver prints

heello World
Sup buddy
this is more

Java Process getInputStream vs. getOutputStream

You can only read from an InputStream, so use that to catch the output of your process.

You write to an OutputStream, so use that to give the process your input.

You are using names that make sense in the context of the spawned process. But the API names make sense in the context of the parent process.

Here's another tip: if your process writes to standard error, be sure to read that too. If standard output or error pipes of the sub-process are full (because your parent Java process isn't consuming them), the child process will block on its write() calls.

Java - How to send a value to child process using outputstream?

Make your child process handling input stream:

public static void main(String[] args) throws Exception {
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, "UTF-8"));

while (!"fin".equals(line = reader.readLine())) {
System.out.println(line);
}

System.out.println("buy");
}

Then in main process you can send messages to child process:

Process proceso = Runtime.getRuntime().exec("java -jar C:\\Users\\Cristian\\Desktop\\java\\numeros.jar");
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(proceso.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(proceso.getInputStream()));
String texto;

out.write(entrada + "\n");
out.flush();

while((texto = in.readLine()) !=null){
...


Related Topics



Leave a reply



Submit