Вопрос: Как получить выход внешней программы, выполненной из Haskell?


Я хочу запустить внешнюю программу из Haskell и получить содержимое своих потоков вывода и ошибок. В одной из библиотек я нашел этот код:

runProcess :: FilePath -> [String] -> IO (ExitCode, String, String)
runProcess prog args = do
  (_,o,e,p) <- runInteractiveProcess prog args Nothing Nothing
  hSetBuffering o NoBuffering
  hSetBuffering e NoBuffering
  sout  <- hGetContents o
  serr  <- hGetContents e
  ecode <- length sout `seq` waitForProcess p
  return (ecode, sout, serr)

Правильно ли это? Есть некоторые вещи, которые я не понимаю здесь: почему потоки настроены на NoBuffering? Зачем length sout 'seq' ? Это похоже на хак. Кроме того, я хотел бы объединить выходные и потоки ошибок в один, чтобы получить тот же эффект, как если бы я 2>&1 в командной строке. Если возможно, я хочу избежать использования выделенных библиотек ввода-вывода и полагаться на стандартные пакеты, поставляемые с GHC.


8


источник


Ответы:


Используйте Shelly, модуль для shell-подобных программ в Haskell:

http://hackage.haskell.org/package/shelly-1.4.1/docs/Shelly.html


4



Эта примерная программа использует process, async, pipes, а также pipes-bytestring пакеты для выполнения внешней команды и записи stdout а также stderr для разделения файлов во время выполнения команды:

import Control.Applicative
import Control.Monad
import Control.Concurrent
import Control.Concurrent.Async
import Control.Exception
import Pipes
import qualified Pipes.ByteString as P
import Pipes.Concurrent
import System.Process
import System.IO

writeToFile :: Handle -> FilePath -> IO ()
writeToFile handle path = 
    finally (withFile path WriteMode $ \hOut ->
                runEffect $ P.fromHandle handle >-> P.toHandle hOut)
            (hClose handle) 

main :: IO ()
main = do
   (_,mOut,mErr,procHandle) <- createProcess $ 
        (proc "foo" ["--help"]) { std_out = CreatePipe
                                , std_err = CreatePipe 
                                }
   let (hOut,hErr) = maybe (error "bogus handles") 
                           id
                           ((,) <$> mOut <*> mErr)
   a1 <- async $ writeToFile hOut "stdout.txt" 
   a2 <- async $ writeToFile hErr "stderr.txt" 
   waitBoth a1 a2
   return ()

И это вариант, который пишет stdout а также stderr чередуется в один файл:

writeToMailbox :: Handle -> Output ByteString -> IO ()
writeToMailbox handle oMailbox = 
     finally (runEffect $ P.fromHandle handle >-> toOutput oMailbox)
             (hClose handle) 

writeToFile :: Input ByteString -> FilePath -> IO ()
writeToFile iMailbox path = 
    withFile path WriteMode $ \hOut ->
         runEffect $ fromInput iMailbox >-> P.toHandle hOut

main :: IO ()
main = do
   (_,mOut,mErr,procHandle) <- createProcess $ 
        (proc "foo" ["--help"]) { std_out = CreatePipe
                                , std_err = CreatePipe 
                                }
   let (hOut,hErr) = maybe (error "bogus handles") 
                           id
                           ((,) <$> mOut <*> mErr)
   (mailBoxOut,mailBoxIn,seal) <- spawn' Unbounded
   a1 <- async $ writeToMailbox hOut mailBoxOut 
   a2 <- async $ writeToMailbox hErr mailBoxOut 
   a3 <- async $ waitBoth a1 a2 >> atomically seal 
   writeToFile mailBoxIn "combined.txt" 
   wait a3
   return ()

Оно использует pipes-concurrent, Нити, которые стекают каждый дескриптор, записываются в один и тот же почтовый ящик. Почтовый ящик считывается основным потоком, который записывает выходной файл во время выполнения команды.


2