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

Graham Fawcett graham.fawcett at gmail.com
Fri May 2 09:55:28 EDT 2008

On Fri, May 2, 2008 at 12:16 AM, Donn Cave <donn at avvanta.com> wrote:
> 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...

Yes, and I should have realized this. After my post, I tried writing
the same program in another language, and ran into the same problems.

Thanks very much for the response. I'm going to play with your
example, and see if I can make it work in this case (and if not, I
will still learn from studying it!).

In case it's of interest, I see that E.W. Karlsen wrote a series of
articles (ten years ago!) on using Haskell to manage asynchronous
processes, and included an "expect-like" tool in his UniForM


By the way, what I'm trying to do is to interact with a Berkeley XML
database; there's no existing Haskell wrapper for its API, and for
this particular task the cost of writing one is too high. I was going
to interact with the 'dbxml' command-line utility as a poor-man's FFI.
Given the issues you've raised, instead I might use a language that
has an existing DBXML interface, and call that from Haskell (e.g.
Python via MissingPy).

Thanks again,


>  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 useless.
>  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 expects
>  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,
> fdRead)
>  pchild :: Fd -> IO () -> IO ()
>  pchild slaveFd exec = do
>         dupTo slaveFd stdInput
>         dupTo slaveFd stdOutput
>         closeFd slaveFd
>         exec
>  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
>  _______________________________________________
>  Haskell-Cafe mailing list
>  Haskell-Cafe at haskell.org
>  http://www.haskell.org/mailman/listinfo/haskell-cafe

More information about the Haskell-Cafe mailing list