I second pseudo-terminals. I find the System.Posix.Terminal to be very usable (though I've admittedly never tried System.Posix.Pty). I occasionally dabble with "bots" for terminal-based games (e.g., Nethack), and have used PTYs with some deal of success. 

I've included some code from a Nethack bot attempt from several years ago. Hopefully you'll find it useful. As a quick usage/rationale overview, I feed an attoparsec parser by repeatedly calling receive (intermixed with some intelligent use of `transmit` to deal with messages and other incomplete screen updates) until I receive a valid map. `hGetNonBlocking` bypasses the standard buffering mechanisms and returns whatever is in the PTY buffer (which may be nothing) rather than waiting for the specified line/block/whatever buffer to fill. `stop` and `start` should be obvious in their purpose, if not their implementation.

data Local = Local { pty :: Handle }

class Connection a where
    transmit :: a -> ByteString -> IO ()
    receive  :: a -> IO ByteString
    stop     :: a -> IO ()
    start    :: IO a

instance Connection Local where
    transmit l s = B.hPut (pty l) s
    receive  l   = B.hGetNonBlocking (pty l) 4096
    stop     l   = hClose $ pty l
    start        = do
        (fd1, fd2) <- openPseudoTerminal
        (hPty) <- fdToHandle fd1
        slave <- fdToHandle fd2
        _<- createProcess (proc "sh" ["-c", "/usr/games/bin/nethack"]){ std_in = (UseHandle slave), std_out = (UseHandle slave) }
        return $ Local hPty


Cheers,
Elliot Robinson
Phone: (321) 252-9660
Site: www.argiopetech.com
Email: elliot.robinson@argiopetech.com

PGP Fingerprint: 0xD1E72E6A9D0610FFBBF838A6FFB5205A9FEDE59A


On Fri, Aug 1, 2014 at 11:20 PM, Brandon Allbery <allbery.b@gmail.com> wrote:
On Fri, Aug 1, 2014 at 11:07 PM, Chris Myzie <cmyzie28@gmail.com> wrote:
As a workaround, I am able to trick the child haskell process into thinking it's running in an interactive terminal by wrapping it with /usr/bin/script:

We discussed this on IRC the other day. Haskell is doing the same thing that C/C++ stdio / iostreams, and most other buffering systems, do: line buffering on terminal-like devices, block buffering on files and pipes. This is generally expected behavior; although it can be confusing to new programmers, ultimately it is more efficient for most programs.

Interactive use like this, especially over pipes, is fairly unusual; normally you're just copying data around /en masse/, and block buffering is far more efficient. Note that line buffering is not and can not be implemented at the kernel level for ordinary files or pipes, so the kernel interface is actually character buffering which is extremely inefficient (at least one context switch per individual character).

You might want to search for something like "buffering line block pipes files" to see quite a lot of discussion about it, in pretty much every language you can think of.

By the way, more efficient than using script(1) is, as I told you in IRC, to use the mechanism it is using directly: pseudo-terminals (ptys). See http://hackage.haskell.org/package/unix-2.7.0.1/docs/System-Posix-Terminal.html#g:6 for the standard pty stuff or http://hackage.haskell.org/package/posix-pty-0.1.0/docs/System-Posix-Pty.html for what is claimed to be a simpler interface intended for what you are doing.

--
brandon s allbery kf8nh                               sine nomine associates
allbery.b@gmail.com                                  ballbery@sinenomine.net
unix, openafs, kerberos, infrastructure, xmonad        http://sinenomine.net

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe