忽略进程的stderr输出

4
这应该是一个简单的问题,但到目前为止我还没有找到任何直接的答案:使用process库如何忽略Haskell进程的stderr(或stdout)?例如,假设我有以下内容:
let proc = (shell "dir /z") {
      std_in  = UseHandle stdin
    , std_out = CreatePipe
    }
(_, out, _, rProc) <- createProcess Proc
exitCode <- waitForProcess rProc

(旁注:在Windows中,我知道dir没有/z开关。这就是为什么我选择它的原因 - 这样我就可以在stderr上获得一些有趣的输出。)
这样做只会导致将stderr打印到控制台。现在假设我想忽略stderr,我该怎么办?
我找到的唯一线索在 此部分process文档中: NoStream 关闭流的文件描述符而不传递句柄。在POSIX系统上,这可能会导致子进程中出现奇怪的行为,因为在关闭文件后尝试读取或写入会抛出错误。这应该仅用于根本不使用文件描述符的子进程。如果您希望忽略子进程的输出,则应创建一个管道并手动排除它,或者传递一个写入到/dev/nullHandle
这有点有用,但仍然留下了一些问题。在非POSIX系统上,NoStream是否可以使用?它提到了创建一个管道,然后排除它,但我找不到任何关于如何做到这一点的信息?/dev/null在Windows上是NUL,除非您使用MSYS或Cygwin,在此情况下它再次是 /dev/null(我想) - 所以我想避免那样做。
因此,重申我的问题:忽略进程的stderr的推荐方法是什么,与操作系统无关?
2个回答

2

1
这绝对是我通常会使用的,但不幸的是由于各种原因我不能使用 typed-process。无论如何,还是谢谢你的回答! - bradrn

0

以下是一种正确的方式:

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

以下是一些关于已删除答案的评论值得注意:

  1. 你应该分叉一个线程来排除错误管道。否则,如果有很多错误,你启动的进程可能会在打印完所有错误之前被杀死,导致混乱的调试过程。
  2. 如果你要使用waitForProcess,你应该分叉一个线程来排除输出管道。否则,在它打印完想要的所有内容之前,它可能会被杀死,导致不完整的输出。
  3. 这将在内存中存储整个进程的输出(虽然不是错误流的全部内容)。这可能非常昂贵。
  4. forceGetContents是强制由hGetContents返回的String完全评估的好方法,但还有其他可能需要更复杂的强制函数的String生产者。另请参见deepseq包中的rnf
您可以解决(2)和(3),如果有一个您知道该进程将遵循的协议,您将会知道何时它完成生成输出。然后可以流式传输输出(减少可早期丢弃的位对内存施加的压力),并延迟waitForProcess,直到您知道它完成输出(避免需要派生线程来排除输出 - 尽管仍需要派生线程处理错误!)。

1
@dfeuer 管道缓冲区通常为4KB左右。因此,如果输出超过4KB,操作系统不会全部缓冲它。 - Daniel Wagner
如果您在等待进程时没有同时排空输出管道,那么输出管道可能会被填满,如果发生这种情况,操作系统将终止程序。 - Daniel Wagner
@bradrn 这个东西很容易自己测试。只需编写一个小型的C程序,其中包含一个循环,循环次数大约为10,000次,并且每次循环都执行fprintf(stderr, "a line to stderr\n"); fprintf(stdout, "a line to stdout\n");(以便产生大量输出到两个句柄并交错输出)。然后,您可以尝试按照您提出的方式更改此答案中的代码,并观察其如何失败。 - Daniel Wagner
@DanielWagner,这解决了我很多问题!那么这是否意味着,如果我运行的程序只有很少量的输出(仅几行),那么应该可以在一个线程中完成所有操作,并将stderr保持未处理状态? - bradrn
@dfeuer 如答案中所讨论的,阻塞也是一种可能性。这取决于程序的编写方式。但是阻塞也同样糟糕:这意味着如果管道填满,并且在您排空管道之前发生waitForProcess,则会出现死锁,其中waitForProcess正在等待管道排空,而您不会排空管道直到waitForProcess返回。是的,如果您读取/处理输出而不使用hGetContents,那就足够了--正如我在答案中所说的! - Daniel Wagner
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接