2012-02-25 5 views
10

¿Cómo puedo definir 'catchOutput' para que la ejecución principal produzca solo 'bar'?Captura/secuestro de stdout en haskell

Es decir, ¿cómo puedo acceder tanto al flujo de salida (stdout) como a la salida real de una acción io por separado?

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

El mejor "solución" hipotético que he encontrado hasta el momento incluye el desvío de la salida estándar, inspired by this, a una secuencia de archivo y luego la lectura de ese archivo (Además de ser súper feo no he sido capaz de leer directamente después de escribir desde un archivo. ¿Es posible crear una "secuencia de almacenamiento intermedio personalizada" que no tenga que almacenar en un archivo?). Aunque eso se siente "un poco" como una canción secundaria.

Otro ángulo parece utilizar 'hGetContents stdout' si se supone que debe hacer lo que creo que debería. Pero no tengo permiso para leer de stdout. Aunque Google parece mostrar que ha sido used.

+0

Dependiendo de su caso de uso, y si la portabilidad es un problema, puede utilizar esta biblioteca experimental: https://hackage.haskell.org/package/system-posix-redirect-1.1.0.1/docs/System-Posix -Redirect.html –

Respuesta

4

¿Por qué no utilizar simplemente una mónada de escritor? Por ejemplo,

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

Como alternativa, puede modificar la acción interior para tomar una Handle escribir en lugar de stdout. A continuación, puede usar algo como knob para crear un identificador de archivo en memoria que pueda pasar a la acción interna y verificar su contenido posteriormente.

+0

La cosa es que realmente no puedo hacer mucho sobre el argumento io en mi código actual. Eso requeriría un rediseño del comportamiento hacia afuera. Se supone que la "aplicación cliente" envía/pasa io acciones (como argumentos). Así que en algún momento u otro tendré que usar algo como 'catchOutput'. Entonces, la mónada del escritor no es realmente aplicable a mi problema. El botón por otro lado parece potencialmente útil. :) – worldsayshi

+1

@worldsayshi Pareces malinterpretar la sugerencia de hammar. Observe que no usó la mónada 'Writer', sino una mónada' WriterT a IO', por lo que todavía puede llamar a su acción 'IO' utilizando' liftIO io'. –

+0

He hecho una corrección en mi código, lo siento. Probablemente algo de confusión por eso.Lo que primero estoy buscando es que el texto termine en stdout. Eso que es 'ahora' 'res', en mi ejemplo. No veo cómo LiftIO me puede dar acceso a eso. Entonces también quiero que la salida también ejecute la acción io. Esa es actualmente la salida ignorada de 'catchOutput'. Si obtengo un 'IO()' como argumento al levantarlo, obtendré una 'm()'. – worldsayshi

1

Como @hammar señaló, se puede utilizar un mando para crear un archivo en memoria, pero también se puede utilizar hDuplicate y hDuplicateTo cambiar stdout en el fichero de la memoria, y viceversa. Algo así como el siguiente código, no está comprobado:

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) 
+3

Hmm, ahora que instalé 'Data.Knob', no puedo hacer que este método funcione. La llamada a 'hDuplicateTo' falla al intentar duplicar el control del mando a' stdout', con el siguiente error: 'Wots: : hDuplicateTo: operación ilegal (los identificadores son incompatibles)'. – pat

3

que utiliza la siguiente función para una unidad de prueba de una función que imprime en la salida estándar.

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 

no estoy seguro sobre el enfoque de archivos en memoria, pero funciona bien para una pequeña cantidad de la producción con un archivo temporal.

4

Hay algunos paquetes en Hackage que prometen hacer eso: io-capture y silenciosamente. silenciosamente parece mantenerse y funciona también en Windows (io-capture solo funciona en Unix). Con silencio, se utiliza la captura:

import System.IO.Silently 

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

Tenga en cuenta que funciona mediante la reorientación de salida a un archivo temporal y luego lo lee ... Pero mientras funciona!