Haskell:System.Process合并stdout和stderr

scr*_*avy 9 haskell pipe process

我想从haskell程序中调用一个进程并捕获stdout以及stderr.

我所做的:

(_, stdout, stderr) <- readProcessWithExitCode "command" [] ""
Run Code Online (Sandbox Code Playgroud)

问题:这样,stdout和stderr被单独捕获,但是我希望消息出现在正确的位置(否则我只是stdout ++ stderr将错误消息与其stdout对应物分开).

我知道如果我将输出传输到文件中,我就可以实现这一点,即

tmp <- openFile "temp.file" ...
createProcess (proc "command" []) { stdout = UseHandle tmp,
                                    stderr = UseHandle tmp }
Run Code Online (Sandbox Code Playgroud)

因此,我目前的解决方法是将输出管道传输到临时文件并重新读取.但是我正在寻找更直接的方法.

如果我在unix上肯定我只是调用一个shell命令ála

command 2>&1
Run Code Online (Sandbox Code Playgroud)

就是这样.但是,我想让它尽可能便携.

我需要这个:我已经构建了一个小的haskell cgi脚本(只是为了玩它),它调用某个程序并打印输出.我想html-escape输出,因此我不能简单地将它传递给stdout.

我在想:也许可以创建一个内存中的句柄,比如Java中的PipedInputStream/PipedOutputStream,或ArrayInputStream/ArrayOutputStream,它允许在内存中处理IO流.我环顾四周寻找一个函数:: Handle,但没有找到任何东西.

也许还有另一个Haskell模块允许我合并两个流?

Gab*_*lez 6

您可以使用pipes并发合并两个输入流.第一个技巧是同时从两个流中读取,您可以使用该stm包执行:

import Control.Applicative
import Control.Proxy
import Control.Concurrent
import Control.Concurrent.STM
import System.Process

toTMVarC :: (Proxy p) => TMVar a -> () -> Consumer p a IO r
toTMVarC tmvar () = runIdentityP $ forever $ do
    a <- request ()
    lift $ atomically $ putTMVar tmvar a

fromTMVarS :: (Proxy p) => TMVar a -> () -> Producer p a IO r
fromTMVarS tmvar () = runIdentityP $ forever $ do
    a <- lift $ atomically $ takeTMVar tmvar
    respond a
Run Code Online (Sandbox Code Playgroud)

我将很快在一个pipes-stm包中提供上述原语,但现在使用上面的原语.

然后你只需将每个进给Handle到一个单独的MVar并同时读取:

main = do
    (_, mStdout, mStderr, _) <- createProcess (proc "ls" [])
    case (,) <$> mStdout <*> mStderr of
        Nothing               -> return ()
        Just (stdout, stderr) -> do
            out <- newEmptyTMVarIO
            err <- newEmptyTMVarIO
            forkIO $ runProxy $ hGetLineS stdout >-> toTMVarC out
            forkIO $ runProxy $ hGetLineS stderr >-> toTMVarC err
            let combine () = runIdentityP $ forever $ do
                    str <- lift $ atomically $
                        takeTMVar out `orElse` takeTMVar err
                    respond str
            runProxy $ combine >-> putStrLnD
Run Code Online (Sandbox Code Playgroud)

只需更改putStrLnD即可处理输入.

要了解有关该pipes包的更多信息,请阅读Control.Proxy.Tutorial.


Ove*_*ang 5

对于 posix 系统,您可以使用createPipefdToHandleinSystem.Posix.IO创建一对新句柄(不过我不确定在哪里关闭这些句柄和 fds..):

  readProcessWithMergedOutput :: String -> IO (ExitCode, String)
  readProcessWithMergedOutput command = do
    (p_r, p_w) <- createPipe
    h_r <- fdToHandle p_r
    h_w <- fdToHandle p_w
    (_, _, _, h_proc) <- createProcess (proc command [])
                                       { std_out = UseHandle h_w
                                       , std_err = UseHandle h_w
                                       }
    ret_code <- waitForProcess h_proc
    content <- hGetContents h_r
    return (ret_code, content)
Run Code Online (Sandbox Code Playgroud)

对于 Windows,这篇文章实现了跨平台createPipe.