Search code examples
haskellprocessstderr

Ignoring stderr of process


This should be a simple question, but so far I haven’t found any direct answer to it: how does one ignore the stderr (or stdout) of a Haskell process using the process library? For instance, let’s say I have the following:

let proc = (shell "dir /z") {
      std_in  = UseHandle stdin
    , std_out = CreatePipe
    }
(_, out, _, rProc) <- createProcess Proc
exitCode <- waitForProcess rProc

(Side note: in Windows, I do know that dir doesn’t have a /z switch. That’s why I chose it — so I could get some interesting output on stderr.)

Doing this just causes stderr to be printed to the console. Now let’s say I want to ignore stderr. How do I do this?

The only clue I have found is in this part of the process documentation:

NoStream Close the stream's file descriptor without passing a Handle. On POSIX systems this may lead to strange behavior in the child process because attempting to read or write after the file has been closed throws an error. This should only be used with child processes that don't use the file descriptor at all. If you wish to ignore the child process's output you should either create a pipe and drain it manually or pass a Handle that writes to /dev/null.

This is somewhat helpful, but still leaves some questions unanswered. On non-POSIX systems, is NoStream OK to use? It refers to creating a pipe then draining it, but I can’t find any information on how to do this? And /dev/null is NUL on Windows, except when you’re using MSYS or Cygwin, when it’s /dev/null again (I think) — so I want to avoid that.

So to reiterate my question: what is the recommended, OS-agnostic way to ignore the stderr of a process?


Solution

  • Here is one correct way:

    import Control.Concurrent
    import Control.Exception
    import System.IO
    import System.Process
    
    forceGetContents :: String -> IO ()
    forceGetContents s = evaluate (length s) >> return ()
    
    main = do
        outMVar <- newEmptyMVar
        let proc = (shell "dir /z") {
              std_in  = UseHandle stdin
            , std_out = CreatePipe
            , std_err = CreatePipe
            }
        (_, Just out, Just err, rProc) <- createProcess proc
        forkIO (hGetContents err >>= forceGetContents)
        forkIO $ do
            s <- hGetContents out
            forceGetContents s
            putMVar outMVar s
        exitCode <- waitForProcess rProc
        outContents <- takeMVar outMVar
        putStr outContents -- or whatever
    

    Some things worth noting from comments on a deleted answer:

    1. You should fork a thread to drain the error pipe. Otherwise, if there are many errors, the process you started may be killed before it can print them all, leading to a confusing debugging session.
    2. If you are going to waitForProcess, you should fork a thread to drain the output pipe. Otherwise it may be killed before it can print everything it wants to, giving incomplete output.
    3. This will store the entire output of the process (though not the entire contents of the error stream) in memory. This can be quite expensive.
    4. forceGetContents is a good way to force a String returned by an hGetContents to be fully evaluated, but there are other String-producers which may need a more involved forcing function. See also rnf from the deepseq package.

    You can address both (2) and (3) if there is a protocol that you know the process will follow where you will know when it is done producing output. Then you can stream the output (reducing memory pressure for bits that can be discarded early), and can delay the waitForProcess until you know it is done outputting (avoiding the need to fork a thread to drain the output -- though still requiring a forked thread for the errors!).