Catching/hijacking stdout in haskell

徘徊边缘 提交于 2019-12-02 23:40:27

Why not just use a writer monad instead? For example,

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")

Alternatively, you could modify your inner action to take a Handle to write to instead of stdout. You can then use something like knob to make an in-memory file handle which you can pass to the inner action, and check its contents afterward.

I used the following function for an unit test of a function that prints to stdout.

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

I am not sure about the in-memory file approach, but it works okay for a small amount of output with a temporary file.

There are some packages on Hackage that promise to do that : io-capture and silently. silently seems to be maintained and works on Windows too (io-capture only works on Unix). With silently, you use capture :

import System.IO.Silently

main = do
   (output, _) <- capture $ putStr "hello"
   putStrLn $ output ++ " world"

Note that it works by redirecting output to a temporary file and then read it... But as long as it works !

As @hammar pointed out, you can use a knob to create an in-memory file, but you can also use hDuplicate and hDuplicateTo to change stdout to the memory file, and back again. Something like the following completely untested code:

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)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!