views:

166

answers:

3

I have been under the impression that processes on the operating system have three standard streams: stdin, stdout, and stderr. I have also thought that text editors like vim work by taking input over stdin and sending ANSI escape characters over stdout. However, my view of how command-line interpreters isn't holding up in this one case:

When I run the command C:\cygwin\bin\bash.exe, I am prompted with:

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

C:\Users\pkalauskas>C:\cygwin\bin\bash.exe
bash-3.2$ 

...but when I run it in Java with the following snippet, the stdin stream is empty:

ProcessBuilder pb = new ProcessBuilder("C:\\cygwin\\bin\\bash.exe");
pb.redirectErrorStream(true);
Process proc = pb.start();
final InputStream in = proc.getInputStream();

new Thread(new Runnable() {
  public void run() {
    // Blocks forever...
    in.read(new byte[1024]);
  }
}).start();

What is going on here? I have been told that bash.exe is running in interactive-mode. Does this mean the standard streams aren't being used? How can I still work work with these programs, and ultimately, how could I implement my own version of cmd.exe? I think I am not understanding something fundamental about how command-line interpreters work...

(Any links to articles discussing related subjects would be very much appreciated. I haven't had much luck searching. Oh, and one last question, are standard streams treated any differently in Windows than in most Unix-like operating systems?)

+1  A: 

I'm more a Python guy than a Java guy (so everything I tell you is quick guesses from JavaDoc), but it looks like you're setting up a multi-process deadlock.

in.read(new byte[1024]); won't return until it's read 1024 bytes of data and bash.exe doesn't output a whole 1024 bytes before stopping to wait for input. (To do that, use proc.getOutputStream() and feed it some commands to respond to.)

As a result, you get Java waiting for bash to respond and bash waiting for Java to respond and both perfectly content to wait until the death of the universe without getting bored or tired.

My advice is to use in.available() before each call to in.read() to avoid blocking. That way, you can switch back and forth between feeding data in and pulling it out without getting stuck.

In fact, it'd probably be a lot simpler and saner to just wrap it in a BufferedReader.

Update from comment: Also, when tools like bash detect that stdin isn't a terminal (see the isatty system call), they buffer in huge (4K or more) chunks on the assumption that the input is non-interactive. I'm not sure if it'll help, but try starting bash with the -i flag.

ssokolow
Sorry, I was actually executing in.read in a new thread, but I left it out for brevity. I'll make an edit.
masson
Ahh. My diagnosis still holds true to some extent. Are you sending commands to bash? ...with line terminators? ...the kind Cygwin on Windows expects? (Not sure whether it wants \n or \r\n)
ssokolow
From the javadoc: "The number of bytes actually read is returned as an integer. This method blocks until input data is available, end of file is detected, or an exception is thrown."I've used this command on other programs, even cygwin's vim-nox.exe, and I've always recieved output. For the issue you are addressing, see my other question: http://stackoverflow.com/questions/3641407/runtime-getruntime-execc-cygwin-bin-bash-exe-has-no-input-to-readFor this question, I am really only asking whether or not processes can send data over anything other than stdout and stderr.
masson
Ahh, sorry about that. As far as I know (I write shell scripts on Linux a fair bit), bash never outputs via something other than stdout/stderr, but if it detects that stdin isn't a terminal (see the isatty system call), it buffers in huge (apparently 4K or more) chunks on the assumption that the input is non-interactive. http://mywiki.wooledge.org/BashFAQ/009 I'm not sure if it'll help, but try starting bash with the -i flag.
ssokolow
That was exactly the problem. Thanks a ton!
masson
+4  A: 

Any program using the c standard library can tell if it is talking to a tty device (a.k.a command line) using the function isatty(). Bash probably detects that it is talking to a pipe instead of a tty and doesn't output a prompt.

ergosys
aye: `$ echo tty | bash` yields "not a tty"
msw
+1  A: 

Being in interactive mode doesn't mean that the standard streams aren't being used. But in this case, Bash is most likely running in non-interactive mode (it's detecting that it's not talking directly to the terminal app, so it assumes it's being used programmatically, and therefore doesn't print the welcome banner). In this case the standard streams are still used, it's just that nothing is being output.

As ergosys pointed out, you can't really rely on in.read(new byte[1024]) returning before it has read the full 1024 bytes, though it's probably ok to assume that it will - however, it certainly won't return before it's read at least one byte, and I think that's the problem here - you're not getting even one byte of output.

Try passing "-i" to bash to get it to run in interactive mode.

davmac