【问题标题】:Haskell: System.Process merge stdout and stderrHaskell:System.Process 合并标准输出和标准错误
【发布时间】:2012-12-11 02:01:10
【问题描述】:

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

我做什么:

(_, stdout, stderr) <- readProcessWithExitCode "command" [] ""

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

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

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

所以我目前的解决方法是将输出通过管道传输到临时文件并将其读回。但是我正在寻找更直接的方法。

如果我确定使用的是 unix,我会简单地调用一个 shell 命令 á la

command 2>&1

就是这样。不过,我希望它尽可能便携。

我需要这个:我已经构建了一个小型的 haskell cgi 脚本(只是为了玩它),它调用某个程序并打印输出。我想对输出进行 html 转义,因此我不能简单地将其通过管道传输到标准输出。

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

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

【问题讨论】:

标签: haskell process pipe


【解决方案1】:

您可以使用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

我很快会在 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

只需将putStrLnD 更改为您想要处理输入的任何内容。

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

【讨论】:

    【解决方案2】:

    对于 posix 系统,您可以在 System.Posix.IO 中使用 createPipefdToHandle 创建一对新句柄(虽然我不确定在哪里关闭这些句柄和 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)
    

    对于 windows,this post 实现了一个跨平台的createPipe

    【讨论】:

      猜你喜欢
      • 2011-06-26
      • 1970-01-01
      • 2014-07-22
      • 1970-01-01
      • 2016-01-10
      • 1970-01-01
      • 1970-01-01
      • 2014-08-25
      • 1970-01-01
      相关资源
      最近更新 更多