Unable to read InputStream from Java Process (Runtime.getRuntime().exec() or ProcessBuilder)
Asked Answered
P

6

28

I'm trying to start a process externally with Java and can't read anything from its InputStream.

If I'm starting a process with commands like "ls", "ps" or "kill" everything works fine. I can start the process and get information either on the InputStream or the ErrorStream of the Process.

If I try to use a command like "ftp" or "telnet" both InputStream and ErrorStream are blocking my program when trying to read. No information is passed through these streams at any time.

Can anyone explain the behavior? Is it just not possible with these commands or do I have an issue with my implementation?

     String processName = _configuration.getProgramCommand().getCommand();
   ProcessBuilder procBuilder = new ProcessBuilder(processName);   

   System.out.println("Starting process "+processName);   
   _proc = Runtime.getRuntime().exec(processName);// procBuilder.start();            

   if(!procBuilder.redirectErrorStream()) {    
    _errorWorker = new ProcessErrorWorker(_proc);
    _errorWorker.start();   
   }

   String proc_start_answer = _configuration.getNextCommand().getCommand();
   System.out.println("Waiting for process answer '"+proc_start_answer+"'");
   BufferedReader input = new BufferedReader(new InputStreamReader(_proc.getInputStream()));      

   String answer = "";  

   try {         
    System.out.println("inputstream ready: "+input.ready());
    answer+=input.readLine(); 
    System.out.println("process answer:  "+answer);
    input.close();        

   } catch(Exception e) {
    System.out.print(e.getMessage());     
   } 
Pianoforte answered 1/7, 2010 at 16:53 Comment(1)
Did you get this issue resolved ? Can you post in the solution ?Happen
E
18

I realize this is question is old, but I just experienced the same problem. In order to fix this I used this code..

 List<String> commandAndParameters = ...;
 File dir = ...; // CWD for process

 ProcessBuilder builder = new ProcessBuilder();
 builder.redirectErrorStream(true); // This is the important part
 builder.command(commandAndParameters);
 builder.directory(dir);

 Process process = builder.start();

 InputStream is = process.getInputStream();

It appears the process is expecting you to also read from the Error stream. The best fix to this is to merge the input and error stream together.

Update

I didn't see that you attempted to read from the error stream also. It could just be that you need to merge them manually with redirectErrorStream(true)

Extinct answered 10/2, 2011 at 15:50 Comment(1)
But why did they name it errorStream if they are getting stdout? Any ideas?Polymorphous
A
6

You need to do this work in a Thread. For example to log the standard output:

Process process = Runtime.getRuntime().exec(command);
LogStreamReader lsr = new LogStreamReader(process.getInputStream());
Thread thread = new Thread(lsr, "LogStreamReader");
thread.start();


public class LogStreamReader implements Runnable {

    private BufferedReader reader;

    public LogStreamReader(InputStream is) {
        this.reader = new BufferedReader(new InputStreamReader(is));
    }

    public void run() {
        try {
            String line = reader.readLine();
            while (line != null) {
                System.out.println(line);
                line = reader.readLine();
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Then you need a second thread for input handling. And you might want to deal with stderr just like stdout.

Apyretic answered 1/7, 2010 at 17:10 Comment(1)
Thank you for your answer. Actually I've been testing it within a thread, in all variations. I tried reading whole lines, reading single chars and reading bytes. No effect. I can't get a single bit out of this InputStream, and it should definitely response. It only works if the started process is terminating (like "ps", "ls"). But I also need it to run with "interactive" processes like ftp and telnet, there must be an issue I'm not aware of. any futher ideas?Pianoforte
P
6

I had this issue with a C program printing to stdout...

The redirectErrorStream(true) solution with a fflush(stdout) in the C code did the trick for me.

Playful answered 12/7, 2011 at 16:35 Comment(0)
P
4

After struggling for days, and trying many options I found a solution. Working on Ubuntu 14.04 x64, with jdk 8 u 25. This codereview post gave me the hint.

The trick is to use InputStream#available() before reading anything with the BufferedReader. I personally have two threads, one for stdout and the other for stderr. Here is a simple running snippet I extrapolated from my program. If you don't want to read all, just jump to readLine()

import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.BufferedReader;
import java.nio.charset.Charset;


public class LogHandler {
    private BufferedReader bufferedReader;
    private InputStream inputStream;
    private Thread thread;
    private boolean isRunning;

    public void open (InputStream inputStream) {
        this.inputStream = inputStream;
        this.bufferedReader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
        isRunning = true ;

        if(thread!=null){
            thread = new Thread (()->{
                while(isRunning) {
                    String line = null;
                    try {
                        line = readLine();
                    } catch (IOException e) {
                        isRunning = false;
                    }
                    if(line == null) {
                        try {
                            Thread.sleep(150);
                        } catch (InterruptedException ie) {
                            isRunning=false;
                            Thread.currentThread().interrupt();
                        }
                    } else {
                        System.out.println(line);
                    }
                }
            });
        } else throw new IllegalStateException("The logger is already running");
        thread.start();
    }

    public void close() throws InterruptedException {
        if(thread!=null){
            thread.interrupt();
            thread.join();
            thread = null;
            IOUtils.closeQuietly(inputStream);
        } else throw new IllegalStateException("The logger is not running");         }

    private String readLine() throws IOException {
        if(inputStream.available()>0) { // <--------- THIS IS IT
            int kar = bufferedReader.read();
            if (kar != -1) {
                StringBuilder buffer = new StringBuilder(30);
                while (kar != -1 && kar != (int) '\n') {
                    buffer.append((char) kar);
                    kar = bufferedReader.read();
                }
                return buffer.toString();
            }
        }
        return null;
    }
}
Pulchritudinous answered 24/1, 2015 at 4:48 Comment(1)
This doesn't tell you whether a complete line is available, so you can still block in readLine().Enthusiast
S
2

I had the exact same problem. Unfortunately

processBuilder.redirectErrorStream(true); 

didn't work for me; it gave me an idea of what is wrong though. Try using following line of code to display stderr contents:

BufferedReader err=
             new BufferedReader(new InputStreamReader(process.getErrorStream())); 

It helped me figure out what was wrong with my terminal commands running through each thread.

Sorkin answered 7/6, 2012 at 9:32 Comment(0)
J
2

I had the same problem with ftp, until I noticed that ftp detects if it is called from a terminal or not.

When ftp is not called from a terminal, it suspends any output to stdout, unless the verbose (-v) option is supplied.

The reason for the streams blocking is that nothing is written to them.

Jakob answered 22/2, 2013 at 12:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.