2012-02-25 12 views
10

Jak zdefiniować "catchOutput" tak, aby główne wyjścia tylko "bar"?Catching/hijacking stdout w haskell

To znaczy, w jaki sposób można uzyskać dostęp zarówno do strumienia wyjściowego (standardowego), jak i rzeczywistego wyjścia działania 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") 

Najlepszym „rozwiązanie” hipotetyczny znalazłem do tej pory obejmuje przekierowanie stdout, inspired by this, do strumienia pliku, a następnie odczyt z tego pliku (Poza tym jest super-brzydki nie byłem w stanie czytać bezpośrednio po napisaniu z pliku Czy możliwe jest utworzenie "niestandardowego strumienia bufora", który nie musi być zapisany w pliku?). Chociaż to "trochę" przypomina boczny tor.

Inny kąt wydaje się używać "hGetContents stdout", jeśli to ma robić to, co myślę, że powinien. Ale nie mam pozwolenia na czytanie ze stdouta. Mimo, że googlowałem, wydaje się, że było to used.

+0

W zależności od Twojego przypadku użycia i tego, czy przenośność jest problemem, możesz użyć tej biblioteki eksperymentalnej: https://hackage.haskell.org/package/system-posix-redirect-1.1.0.1/docs/System-Posix -Redirect.html –

Odpowiedz

4

Dlaczego zamiast tego należy użyć monitu pisarskiego? Na przykład,

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

Alternatywnie, można modyfikować swoje wewnętrzne działania, aby wziąć Handle zapisu zamiast stdout. Następnie możesz użyć czegoś takiego, jak knob, aby utworzyć uchwyt pliku w pamięci, który możesz przekazać do działania wewnętrznego, a następnie sprawdzić jego zawartość.

+0

Rzecz w tym, że tak naprawdę nie mogę wiele zrobić z argumentem io w moim rzeczywistym kodzie. To wymagałoby przeprojektowania zachowania na zewnątrz. "Aplikacja kliencka" powinna wysyłać/przekazywać akcje (jako argumenty). Tak więc w którymś momencie będę musiał użyć czegoś takiego jak "catchOutput". Tak więc pisarska monada naprawdę nie pasuje do mojego problemu. Gałka z drugiej strony wydaje się być potencjalnie użyteczna. :) – worldsayshi

+1

@worldsayshi Wydajesz się źle rozumieć sugestię hammara. Zauważ, że nie użył monady 'Writer', ale monadę' WriterT a IO' - więc nadal możesz wywoływać akcję 'IO' używając' liftIO io'. –

+0

Wprowadziłem korektę do mojego kodu, przepraszam za to. Prawdopodobnie jakieś zamieszanie.Po pierwsze szukam tekstu, który kończy się na stdout. To, co jest _now_ "res", w moim przykładzie. Nie rozumiem, jak liftIO daje mi dostęp do tego. Następnie chcę również, aby ouput również uruchomił działanie io. To jest obecnie ignorowane wyjście z "catchOutput". Jeśli otrzymam "IO()" jako podniesienie argumentu, otrzymam "m()". – worldsayshi

1

Jak @hammar wskazał, można użyć pokrętła, aby utworzyć plik w pamięci, ale można też użyć hDuplicate i hDuplicateTo zmienić stdout do pliku pamięci iz powrotem. Coś jak następujący kod całkowicie niesprawdzonych:

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, teraz, gdy faktycznie zainstalowałem 'Data.Knob', nie mogę uruchomić tej metody. Wywołanie 'hDuplicateTo' kończy się niepowodzeniem, gdy próbuje się zduplikować uchwyt pokrętła na' stdout', z następującym błędem: 'Wots: : hDuplicateTo: operacja niedozwolona (uchwyty są niezgodne)'. – pat

3

użyłem następującą funkcję dla testu jednostkowego funkcja, która drukuje na standardowe wyjście.

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 

Nie mam pewności co do podejścia do zapisu w pamięci, ale działa dobrze dla niewielkiej ilości danych wyjściowych z plikiem tymczasowym.

4

Istnieje kilka pakietów na Hackage, które obiecują to zrobić: io-capture i cicho. cicho wydaje się być utrzymywany i działa również na Windows (io-capture działa tylko na systemie Unix). Z dyskretnie, należy użyć przechwytywania:

import System.IO.Silently 

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

pamiętać, że działa poprzez przekierowanie wyjścia do pliku tymczasowego, a następnie ją przeczytać ... Ale tak długo, jak to działa!