【问题标题】:Catching/hijacking stdout in haskell在haskell中捕获/劫持标准输出
【发布时间】:2012-02-25 20:14:46
【问题描述】:

如何定义 'catchOutput' 以便运行主输出仅 'bar'?

也就是说,如何分别访问输出流 (stdout) 和 io 操作的实际输出?

catchOutput :: IO a -> IO (a,String)
catchOutput = undefined

doSomethingWithOutput :: IO a -> IO ()
doSomethingWithOutput io = do
   (_ioOutp, stdOutp) <- catchOutput io
   if stdOutp == "foo"
      then putStrLn "bar"
      else putStrLn "fail!"

main = doSomethingWithOutput (putStr "foo")

到目前为止,我发现的最好的假设“解决方案”包括将标准输出 inspired by this 转移到文件流中,然后从该文件中读取(除了超级丑陋之外,我无法读取从文件写入后直接。是否可以创建一个不必存储在文件中的“自定义缓冲流”?)。虽然这感觉“有点”像边路。

如果应该做我认为应该做的事情,另一个角度似乎使用了“hGetContents stdout”。但我没有被允许从标准输出读取。虽然谷歌搜索似乎表明它是used

【问题讨论】:

标签: haskell stdout handle


【解决方案1】:

我使用以下函数对打印到标准输出的函数进行单元测试。

import GHC.IO.Handle
import System.IO
import System.Directory

catchOutput :: IO () -> IO String
catchOutput f = do
  tmpd <- getTemporaryDirectory
  (tmpf, tmph) <- openTempFile tmpd "haskell_stdout"
  stdout_dup <- hDuplicate stdout
  hDuplicateTo tmph stdout
  hClose tmph
  f
  hDuplicateTo stdout_dup stdout
  str <- readFile tmpf
  removeFile tmpf
  return str

我不确定内存文件方法,但它适用于带有临时文件的少量输出。

【讨论】:

    【解决方案2】:

    Hackage 上有一些包可以做到这一点:io-capture 和静默。似乎默默地维护并且也可以在 Windows 上运行(io-capture 仅适用于 Unix)。默默地使用捕获:

    import System.IO.Silently
    
    main = do
       (output, _) <- capture $ putStr "hello"
       putStrLn $ output ++ " world"
    

    请注意,它通过将输出重定向到一个临时文件然后读取它来工作......但只要它工作!

    【讨论】:

      【解决方案3】:

      为什么不直接使用 writer monad 呢?例如,

      import Control.Monad.Writer
      
      doSomethingWithOutput :: WriterT String IO a -> IO ()
      doSomethingWithOutput io = do
         (_, res) <- runWriterT io
         if res == "foo"
            then putStrLn "bar"
            else putStrLn "fail!"
      
      main = doSomethingWithOutput (tell "foo")
      

      或者,您可以修改您的内部操作以使用Handle 而不是stdout 来写入。然后,您可以使用knob 之类的东西来创建一个内存中的文件句柄,您可以将其传递给内部操作,然后检查其内容。

      【讨论】:

      • 问题是,我对实际代码中的 io 参数做不了太多事情。这将需要重新设计外部行为。 “客户端应用程序”应该发送/传递 io 操作(作为参数)。所以在某些时候,我将不得不使用'catchOutput'之类的东西。所以作家单子并不真正适用于我的问题。另一方面,旋钮似乎很有用。 :)
      • @worldsayshi 您似乎误解了 hammar 的建议。请注意,他没有使用 Writer monad,而是使用 WriterT a IO monad - 所以您仍然可以使用 liftIO io 调用您的 IO 操作。
      • 我对我的代码进行了更正,对此感到抱歉。可能会因此而感到有些困惑。我首先要寻找的是以标准输出结尾的文本。在我的示例中,这就是 now 'res'。我不知道liftIO 可以如何让我访问它。然后我也想要实际运行 io 动作的输出。这是当前“catchOutput”被忽略的输出。如果我得到一个 'IO ()' 作为参数解除它会给我一个 'm ()'。
      • 将“res”的名称更改为“stdOutp”。
      【解决方案4】:

      正如@hammar 所指出的,您可以使用旋钮创建内存文件,但也可以使用hDuplicatehDuplicateTostdout 更改为内存文件,然后再返回。类似于以下完全未经测试的代码:

      catchOutput io = do
        knob <- newKnob (pack [])
        let before = do
              h <- newFileHandle knob "<stdout>" WriteMode
              stdout' <- hDuplicate stdout
              hDuplicateTo h stdout
              hClose h
              return stdout'
            after stdout' = do
              hDuplicateTo stdout' stdout
              hClose stdout'
        a <- bracket_ before after io
        bytes <- Data.Knob.getContents knob
        return (a, unpack bytes)
      

      【讨论】:

      • 嗯,现在我实际安装了Data.Knob,我无法让这个方法起作用。尝试将旋钮句柄复制到 stdout 时调用 hDuplicateTo 失败,出现以下错误:Wots: &lt;stdout&gt;: hDuplicateTo: illegal operation (handles are incompatible)
      猜你喜欢
      • 1970-01-01
      • 2017-06-17
      • 1970-01-01
      • 2021-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-11
      相关资源
      最近更新 更多