How can I combine Handles in Haskell?
I'd like to have something like bash's 2>&1
redirect in Haskell that combines stdout
and stderr
from a process into a single Handle
. It would be nice to do this directly with System.Process.createProcess
or a similar library function, particularly if it used the same开发者_JAVA百科 semantics as the bash redirect w.r.t. interleaving input from the handles.
The flexibility offered by createProcess
seems promising at first: one can specify a Handle
to use for the standard file descriptors, so the same Handle
could be given for both stdout
and stderr
. However, the Handle
arguments must already exist before the call. Without the ability to create a Handle
from thin air before calling the function, I'm not sure the problem can be solved this way.
Edit: The solution needs to work regardless of platform.
From here:
import GHC.IO.Handle -- yes, it's GHC-specific
import System.IO
main = do
stdout_excl <- hDuplicate stdout
hDuplicateTo stderr stdout -- redirect stdout to stderr
putStrLn "Hello stderr" -- will print to stderr
hPutStrLn stdout_excl "Hello stdout" -- prints to stdout
Getting your hands on a Handle
isn't too hard: System.IO
offers constants stdin,stdout,stderr :: Handle
and functions withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
and openFile :: FilePath -> IOMode -> IO Handle
.
Alternately, you could request new pipes from createProcess
and set yourself up as a forwarding service (reading from the new stdout
and stderr
handles of your child and sending both to wherever you like).
Since Windows supports pipe (3)
natively and GHC's IO library uses CRT file descriptors internally on Windows, it is possible to come up with a solution that works on both Windows and *nix at least. The basic algorithm is:
- Call
pipe
. This gives you two file descriptors which you can convert toHandles
withfdToHandle'
. - Call
createProcess
withstdout
andstderr
both set to the pipe's write end. - Read child output from the pipe's read end.
The System.Posix.Internals
module, which exports c_pipe
, is hidden by default, but you can compile a custom version of base
that lets you access it (tip: use cabal-dev). Alternatively, you can access pipe
via FFI. NB: this solution is GHC-specific.
精彩评论