Question

I'm trying to find a way to invoke an interactive command-line program so that it takes input both directly from the keyboard and also from a named pipe. My goal is to get this working with MATLAB, but I think Bash works just as well as an example. So the idea is to start Bash and once it's running I can type commands, use up arrow for history, etc, and also send commands to a named pipe. I've been looking around and fiddling with this for a few days, but nothing I've tried so far has worked quite right.

For example, there's a helpful thread at https://serverfault.com/questions/171095/how-do-i-join-two-named-pipes-into-single-input-stream-in-linux that suggests doing something like this:

mkfifo alt_in
(tail -f alt_in & cat) | bash

This is almost exactly what I'm looking for, except that if you try to use backspace or arrow keys it doesn't work right. (I guess this is because cat is intercepting the keystrokes, which would normally be handled by the readline library in bash?) Does anyone have other suggestions?

Ultimately I'd like to have a way to launch a MATLAB process so that I can send commands to it from TextMate but also interact with it in the terminal. I use MATLAB for work, but am no great fan of its GUI or editor.

Edit December 19, 2010

Thanks for all the very helpful suggestions! I wanted to summarize the outcome of this for anyone who's interested. I looked into rlwrap, but since my goal was to get this working inside a TextMate bundle that anyone could use, I was hesitant to rely on a non-standard utility (which I should have mentioned in the first place). I also checked on expect and unbuffer briefly, but, well, they seem pretty complicated and I just didn't have the fortitude to dive in to that.

screen worked pretty well for this - as suggested below, I could open Terminal, start screen, start matlab -nodesktop, and then it was possible to have TextMate, for example, send the selected text to Matlab using screen -X ... inside a TextMate command. The shortcomings of this approach that I noticed were:

  1. Sending a block of text with more that 1,024 characters resulted in an error from `screen`; I figure there's some buffer somewhere that can't hold more than that, but didn't spend any time trying to track it down.
  2. Screen has its own scrollback buffer, making Terminal's scrollbar pretty useless. Maybe there's an option to change how this works?
  3. Control-A is `screen`'s command keystroke; you can't use it for moving to the beginning of the line unless you rebind the command key.

While I was playing around with screen, it occurred to me that although the question I asked is about connecting pipes and STDIN and so on, I actually only care about this for a very specific case: getting text from TextMate to Terminal. And that prompted me to try doing this with AppleScript, and surprisingly that turned out to be simpler and more robust than any of the other things I'd tried. Create a TextMate command that takes the selected text or current line as input, and contains

#!/usr/bin/env osascript

set input_command to do shell script "cat"

tell application "Terminal"
    do script input_command in window 1
end tell

And that works great, as long as MATLAB is in the frontmost Terminal window. (It's also possible to search for a Terminal tab that's running MATLAB, I just excluded that for clarity.)

The moral of this story is that I should ask more specific questions. Thanks again for all the help; I've learned a lot from wrestling with this!

Was it helpful?

Solution

Give this a try:

mkfifo alt_in
(tail -f alt_in & rlwrap cat) | bash

You won't get a prompt, but you can enter commands and use arrow keys, etc., including history retrieval. You can echo commands from another terminal into the named pipe and they'll be executed at the receiving end, but they won't be in its history.

Some other things to investigate:

  • expect
  • screen -x and screen -X

For the latter, in one terminal start screen. You'll be able to interact with Bash there pretty much as you normally would. Now on another terminal, do

screen -ls

to determine the PID of the screen session you started. It will be the digits at the start of a line that looks like this:

31544.pts-2.hostname     (01/01/2010 01:01:01 PM)        (Attached)

Now you can do things like this:

screen -S 31544 -X stuff $'echo Your ad here.\n'

which will cause the echo command to be executed on the other terminal. The $'' causes the escape sequences within to be interpreted, in this case giving us a newline so we can "press" Enter.

Let's say we did this:

screen -S 31544 -X stuff $'top\n'

Now top is running. We could go to that terminal and press q to quit, but where's the fun in that?

screen -S 31544 -X stuff 'q'

Notice this time we didn't need a newline.

OTHER TIPS

Try piping the data to bash through unbuffer.

Keyboard input comes in different flavours called "raw mode", "cbreak mode" and "cooked mode".

Raw mode is where the OS passes every keystroke as you type it to the program (bash, for example).

Cooked mode is where the OS buffers up a line and allows you to do simple editing such as deleting characters. When you press RETURN or EOF (usually Ctrl-D) it will send a line to the program.

The stty command allows you to define a number of keycodes that are interpreted as line-end (RETURN), EOF (Ctrl-D) and a bunch of others.

Cbreak-mode is a halfway-house where many of the control-characters retain their usual function but the OS still sends characters one at a time to the application. Programs like bash, vi, and less generally use this mode.

If you set your terminal into raw-mode, then you can no longer use your keyboard to "break out" of it unless the program you're running co-operates.

Expect (here or here) can present a terminal interface to Bash so that it still thinks it's running from a normal keyboard and will still do all the cool bash command-line editing that we're used to.

And Expect can read input from a keyboard in raw mode and a pipe and seamlessly pass it through.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top