在 haskell 中捕获/劫持标准输出

发布于 2025-01-08 20:13:02 字数 1137 浏览 5 评论 0原文

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

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

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

到目前为止,我发现的最好的假设“解决方案”包括转移标准输出, 受此启发,到文件流,然后从该文件中读取(除了超级难看之外,我还无法在从文件写入后直接读取。是否有可能创建一个不必存储在文件中的“自定义缓冲流”?)。尽管这感觉“有点”像是一条岔路。

另一个角度似乎使用“hGetContents stdout”,如果这应该做我认为应该做的事情。但我没有获得从标准输出读取的权限。尽管谷歌搜索似乎表明它已经已使用

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

哭了丶谁疼 2025-01-15 20:13:02

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

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 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.

卸妝后依然美 2025-01-15 20:13:02

Hackage 上有一些软件包承诺可以做到这一点:io-capture 和默默地。似乎默默地被维护并且也可以在 Windows 上工作(io-capture 仅在 Unix 上工作)。静默地,您可以使用 capture :

import System.IO.Silently

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

请注意,它的工作原理是将输出重定向到临时文件,然后读取它......但只要它有效!

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 !

哭了丶谁疼 2025-01-15 20:13:02

为什么不直接使用 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创建一个内存中的文件句柄,您可以将其传递给内部操作,然后检查其内容。

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.

隐诗 2025-01-15 20:13:02

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

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)

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文