Carlo Hamalainen

Reading stdout and stderr of createProcess


For some time I've used this little utility to run a command with parameters and return stdout if the command exited successfully, or stdout and stderr if there was an error:

readRestOfHandle :: Handle -> IO String
readRestOfHandle h = do
    ineof <- hIsEOF h
    if ineof
        then return ""
        else hGetContents h

runShellCommand :: FilePath -> [String] -> IO (Either String String)
runShellCommand cmd args = do
    (Just _, Just hout, Just herr, h) <- createProcess (proc cmd args){ std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe }

    stdOut <- readRestOfHandle hout
    stdErr <- readRestOfHandle herr

    exitCode <- waitForProcess h
    case exitCode of ExitSuccess -> return $ Right stdOut
                     _           -> return $ Left $ stdOut ++ "\n\n\n" ++ stdErr

Unfortunately this code is prone to deadlocking - I had this happen on a call to dcm2mnc. It ran dcm2mnc and then hung.

The fix was to use process-streaming which provides a wrapper for createProcess:

simpleSafeExecute :: PipingPolicy String a -> CreateProcess -> IO (Either String a)

And it's straightforward to use it to get the stdout and stderr separately:

x <- simpleSafeExecute (pipeoe $ separated (surely B.toLazyM) (surely B.toLazyM)) (proc cmd args)

Here's my commit where I switched to process-streaming:

A discussion on haskell-cafe where I found out about process-streaming: