Writing an 'expect'-like program with runInteractiveCommand

Hi folks, 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] ... main = do handles <- connectToExternalProcess sendCmd handles "do something" resp <- sendCmd "get results" -- needs strict I/O, before "quit"? sendCmd "quit" mapM_ putStrLn resp I've tried using runInteractiveCommand, and several combinations of hFlush, hWaitForInput, etc., but I can't find a combination that actually works. I know this is a sketchy description, but can anyone offer some sample code, or point me toward a program that has similar behaviour? Thanks, Graham

Are you adjusting 'System.IO.hSetBuffering' to NoBuffering for those handles? Graham Fawcett wrote:
Hi folks,
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] ...
main = do handles <- connectToExternalProcess sendCmd handles "do something" resp <- sendCmd "get results" -- needs strict I/O, before "quit"? sendCmd "quit" mapM_ putStrLn resp
I've tried using runInteractiveCommand, and several combinations of hFlush, hWaitForInput, etc., but I can't find a combination that actually works.
I know this is a sketchy description, but can anyone offer some sample code, or point me toward a program that has similar behaviour?
Thanks, Graham

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@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

On Fri, May 2, 2008 at 12:16 AM, Donn Cavewrote: > 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 Workbench: http://www.informatik.uni-bremen.de/~ewk/WB.html 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, Graham > 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@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@haskell.org > http://www.haskell.org/mailman/listinfo/haskell-cafe >
participants (3)
-
ChrisK
-
Donn Cave
-
Graham Fawcett