[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
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
More information about the Haskell-Cafe
mailing list