[Haskell-cafe] Re: Writing an 'expect'-like program with runInteractiveCommand

Donn Cave donn at avvanta.com
Fri May 2 00:16:29 EDT 2008

Graham Fawcett wrote:

> I would like to communicate with an external, line-oriented process,
> which takes a sequence of one-line commands, each returning an
> arbitrary number of lines, and waits for another command after each
> response.  So, something like:
> sendCmd :: (Handle, Handle) -> String -> IO [String]

You may need to make some compromises to get this within the realm
of the possible, if I understand your objective.

- There is no way (at least on UNIX) to know when a read has been posted
    on the other end of your pipe/socket/pty/whatever.  So you can't  
tell in this
    way when the external process has sent the last of the arbitrary  
number of
    lines and is now waiting for another command.  If you have  
another way to
    know, or it doesn't really matter, then fine.

- The vast majority of `line-oriented' software are actually going to  
block buffer
    their output when writing to a pipe, because that's what C I/O  
does.  If you're
    lucky, the program you're dealing with here will flush its output  
before it reads,
    but if it doesn't, you're hosed - there isn't any way to talk to  
this program
   `interactively' on a pipe.  In this case, you need a pseudotty  
device, a sort of
    pipe that supports tty device ioctls.

- Buffering on your side of the I/O is of course also worse than  

I see the GHC 6.8 library supports pseudottys, so for general amusement
I submit below a small demonstration program.  Unfortunately it doesn't
entirely work.  The pseudotty works, but I'm unable to turn off ECHO  
on the
slave.  So each master line yields two slave lines, and my program  
only one and gets behind on that account.  So the command has to include
its own "stty -echo".  The commented lines attempt to turn of ECHO,  
but on
MacOS X that causes the program to fail mysteriously.

	Donn Cave, donn at avvanta.com
import System.Posix.Terminal (TerminalMode(..), TerminalState(..),  
withoutMode, getTerminalAttributes, setTerminalAttributes,  
openPseudoTerminal, getSlaveTerminalName)
import System (getArgs)
import System.Posix.Types (Fd, ProcessID)
import System.Posix.Process (forkProcess, executeFile)
import System.Posix.IO (stdInput, stdOutput, closeFd, dupTo, fdWrite,  

pchild :: Fd -> IO () -> IO ()
pchild slaveFd exec = do
         dupTo slaveFd stdInput
         dupTo slaveFd stdOutput
         closeFd slaveFd

ptyOpen :: IO () -> IO (ProcessID, Fd)
ptyOpen exec = do
         (master, slave) <- openPseudoTerminal
         -- tc <- getTerminalAttributes slave
         -- let tc = withoutMode tc EchoLF
         -- setTerminalAttributes slave tc WhenDrained
         pid <- forkProcess (pchild slave exec)
         closeFd slave
         return (pid, master)

ioact p0 p1 m0 m1 = do
         (d, n) <- fdRead m0 512
         fdWrite p1 d
         (e, n) <- fdRead p0 512
         fdWrite m1 e
         ioact p0 p1 m0 m1

main = do
         (cmd:args) <- getArgs
         (pid, fd) <- ptyOpen (executeFile cmd True args Nothing)
         ioact fd fd stdInput stdOutput

More information about the Haskell-Cafe mailing list