Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching/hijacking stdout in haskell

How can I define 'catchOutput' so that running main outputs only 'bar'?

That is, how can I access both the output stream (stdout) and the actual output of an io action separately?

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

The best hypothetical "solution" I've found so far includes diverting stdout, inspired by this, to a file stream and then reading from that file (Besides being super-ugly I haven't been able to read directly after writing from a file. Is it possible to create a "custom buffer stream" that doesn't have to store in a file?). Although that feels 'a bit' like a side track.

Another angle seems to use 'hGetContents stdout' if that is supposed to do what I think it should. But I'm not given permission to read from stdout. Although googling it seems to show that it has been used.

like image 625
worldsayshi Avatar asked Feb 25 '12 20:02

worldsayshi


2 Answers

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.

like image 136
jxy Avatar answered Oct 22 '22 14:10

jxy


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 !

like image 21
Jedai Avatar answered Oct 22 '22 13:10

Jedai