pipe through external process?

Eelco Dolstra edolstra@students.cs.uu.nl
Tue, 17 Jul 2001 11:28:56 +0200 (MET DST)


On Mon, 16 Jul 2001, John Meacham wrote:

  > I am trying to implement a function which takes a string, pipes it
  > through an external command and returns the stdout of teh command
  > utilizing the Posix library and ghc. 
  > first of all, if someone has this handy code tidbit already written then
  > feel free to send it to me. the other thing is that I cannot figure out
  > how to get data into and out of the process. I can create a pipe which
  > returns a pair of Fd's but then i cannot pass these into runProcess
  > since it expects handles. also I have no ability to wait for the
  > termination of the command that runProcess starts... 

Use forkProcess/executeFile instead.  Below is some code I hacked together 
a while back to execute shell commands and read the output (lazily or
strictly).  It could easily be adapted to write something to the
child (use fdWrite in the parent branch to write to writep; dup
readp into stdInput in the child branch), but there is a risk of deadlock. 

module Main where {

import System;
import IOExts;
import Posix;

-- "shell' lazy command" executes the specified command in a child process
-- (by specifying it as an argument to "/bin/sh -c") and returns the data 
-- written to standard output in a string.  If "lazy" is True, the data is 
-- read lazily, i.e., on demand; the child will block if the pipe is full.
-- Otherwise, the data is read at once.
shell' :: Bool -> String -> IO String;
shell' lazy command = do {

    -- Create a pipe.
    (readp, writep) <- createPipe;
    -- Fork.
    child <- forkProcess;
    case child of {
    	-- The parent goes here.
        Just pid -> do {
	    fdClose writep;
	    readFD lazy readp;
	-- The child goes here.
	Nothing -> do {
	    fdClose readp;
	    dupTo writep stdOutput;
	    	(executeFile "/bin/sh" False [ "-c", command ] Nothing)
		(\_ -> exitImmediately (ExitFailure 1));
	    return undefined;

-- Read this many bytes at a time.
granularity = 1024 :: Int;

readFD :: Bool -> Fd -> IO String;
readFD lazy fd = (if lazy then unsafeInterleaveIO else id) $ do {
    (s, bytes) <- catch (fdRead fd granularity) (\_ -> return ("", 0)) ;
    if bytes > 0 then do {
    	t <- readFD lazy fd;
    	return $ s ++ t;
    } else do {
    	fdClose fd;
    	return s;

shell = shell' False;
shellLazy = shell' True;

main = do {
    s <- shellLazy "find /";
    putStr $ unlines $ take 10 $ lines s;