views:

207

answers:

2

I'm trying to execute a new process and read from its input stream in Java. I have successfully used Runtime.getRuntime().exec(String) to start and receive input from several processes. However, when I try to use exec on some other processes, the input stream's read method blocks, and it appears that there is no input. What might be causing the input stream to be empty for some of these processes? Specifically, I am wondering why bash.exe is not outputting anything.

I have written a JUnit test case to demonstrate this issue:

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import junit.framework.TestCase;

public class TestExec extends TestCase {

    public void testExec() throws IOException {
        List<InputPrinter> threads = new ArrayList<InputPrinter>();

        // Create a process for each of the commands and make sure that
        // it outputs at least one line to its input stream.
        threads.add(testExec("cmd"));
        threads.add(testExec("java"));
        threads.add(testExec("C:/cygwin/bin/vim-nox.exe"));

        // These bottom two fail, even though executing these
        // commands in cmd.exe results in immediate output
        threads.add(testExec("javac"));
        threads.add(testExec("C:/cygwin/bin/bash.exe"));

        // Give the threads a second to execute
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            fail();
        }

        // Test that each command had input to read
        for(InputPrinter ip : threads) {
            assertTrue(ip.command + " has not read any input", ip.hasRead);
        }
    }

    // Starts a process for the given command and returns an
    // InputPrinter that can be used to check if the process
    // has had an input to read.
    public InputPrinter testExec(String command) throws IOException {
        Process proc = Runtime.getRuntime().exec(command);
        InputStream in = proc.getInputStream();

        InputPrinter ip = new InputPrinter(in, command);
        new Thread(ip).start();

        return ip;
    }

    // Simple Runnable to read from an InputStream. hasRead will be
    // true if at least one input has been read from the stream
    private class InputPrinter implements Runnable {
        InputStream in;
        String command;
        boolean hasRead;

        public InputPrinter(InputStream in, String command) {
            this.in = in;
            this.command = command;
            this.hasRead = false;
        }

        // Loop indefinitely while printing any received input
        public void run() {
            try {
                final byte[] b = new byte[1024];
                while (true) {
                    int n = in.read(b);
                    if (n > 0) {
                        System.out.print(new String(Arrays.copyOf(b, n)));
                        hasRead = true;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                fail();
            }
        }
    }

}

EDIT:

As far as I know, if a program isn't using stdout or stderr, I shouldn't see anything in the windows command prompt. What I am expecting to see when I start the bash process is "bash-3.2$," the same thing I see when I open the command prompt and run "bash.exe":

Microsoft Windows [Version 6.1.7600]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\cygwin\bin>bash.exe
bash-3.2$
+2  A: 

Regardless of Java, as far as I know you can pipe output (or input) from/to bash only when it is running as a script, not when it is running as an interactive shell (in which case you can only pass cmd parameters to it).

In other words, when you run bash from cmd as you mention in the comment, you see output, but it is contained in the bash process, it is not output that bash sends back to the parent cmd process.

Regarding the javac process, it is actually sending the output to the error stream. Try running from cmd javac 1>null and javac 2>null and you'll see the difference.
Have you looked at the api here? You can try to use ProcessBuilder and redirect the error stream back to the primary input stream, it'll be much easier to work with the processes this way.

Yoni
If what you said about bash is true, then why can I run the bash.exe executable from the command prompt? (see question for edits)
masson
that is because bash is running in interactive mode so it is taking over the cmd window. It only appears as if it is writing to stdout. Try running 'edit' from cmd, for example, it is a native ms-dos app. It completely changes the screen, and yet it is only a text-based app, not a graphical one
Yoni
Hm, it seems I'm not understanding something about the way processes feed output to command-line interpreters. I've posted a new question: http://stackoverflow.com/questions/3645040/how-do-command-line-interpreters-work
masson
+2  A: 

A process typically has not only one but two output streams associated with it. These are:

  1. stdout, which can be read with getInputStream()
  2. stderr, which can be read with getErrorStream()

Javac writes to stderr, not stdout, so you don't read its output.

Because it is inconvenient to have to read both of them (Some years ago, I had to write an extra thread for this), they introduced a new API to system processes, namely the ProcessBuilder, which allows to redirect stderr to stdout.

Just replace the lines

    Process proc = Runtime.getRuntime().exec(command);
    InputStream in = proc.getInputStream();

with

    ProcessBuilder pb = new ProcessBuilder(command);
    pb.redirectErrorStream(true);
    Process proc = pb.start();

, add the required imports, and your test succeeds :).

Daniel
Thanks, didn't even think of that! Unfortunately I'm still having troubles with bash.exe... might be due to my misunderstanding of how the program works though.
masson