Reputation: 1429
I want to run an external program from Haskell and retrieve the contents of its output and error streams. In one of the libraries I found this code:
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)
Is this the right way to do it?
There are some things I don't understand here: why streams are set to NoBuffering
? Why length sout `seq`
? This feels like some kind of hack.
Also, I would like to merge output and error streams into one to get the same effect as if I did 2>&1
on the command line. If possible, I want to avoid using dedicated I/O libraries and rely on standard packages provided with GHC.
Upvotes: 11
Views: 2811
Reputation: 34353
I find readProcessWithExitCode
for this purpose very succinct.
Here's an example using only functions from GHC's standard libraries. The program lists the files of your home directory sorted by size, printing the process' exit code as well as the contents of the standard out and standard error streams:
import System.Directory ( getHomeDirectory )
import System.Process ( readProcessWithExitCode )
import System.Exit ( ExitCode )
import Data.List.NonEmpty
callCmd :: NonEmpty String -> IO (ExitCode, String, String)
callCmd (cmd :| args) = readProcessWithExitCode cmd args stdIn
where stdIn = ""
main = do
home <- getHomeDirectory
(exitCode, stdOut, stdErr) <-
callCmd $ "ls" :| [home, "--almost-all", "-l", "-S"]
putStrLn
$ "Exit code: "
++ show exitCode
++ "\nOut: "
++ stdOut
++ "\nErr: "
++ stdErr
Upvotes: 4
Reputation: 27766
This example program uses the process
, async
, pipes
, and pipes-bytestring
packages to execute an external command and write stdout
and stderr
to separate files while the command runs:
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 ()
And this is a variation that writes stdout
and stderr
interleaved to the same file:
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 ()
It uses pipes-concurrent
. The threads that drain each handle write to the same mailbox. The mailbox is read by the main thread, which writes the output file while the command runs.
Upvotes: 4
Reputation: 1575
Use Shelly, a module for shell-like programming in Haskell:
http://hackage.haskell.org/package/shelly-1.4.1/docs/Shelly.html
Upvotes: 4