Reputation: 452
Suppose I have a list of String
s representing shell commands to execute.
commands = ["git clone https://github.com/test/repo.git", "git checkout origin"]
Also, suppose I have a command, execCommand
that takes a string, executes it as a shell command, retrieves the exit code, stdout and stderr, and, if the exit code is nonzero, returns Just
the concatenation of stdout and stderr; otherwise, it returns Nothing
.
Now, how would I execute that list of commands sequentially while ensuring that subsequent commands do not execute after one command yields an error?
Below is the full code for execCommand
.
import System.IO
import System.Process
import System.Exit
createCommand :: String -> FilePath -> CreateProcess
createCommand command curDir =
(shell command){std_out = CreatePipe, std_err = CreatePipe, cwd = Just curDir}
execCommand :: String -> FilePath -> IO (Maybe String)
execCommand command curDir = do
(_, Just hout, Just herr, procHandle) <- createProcess $ createCommand command curDir
exitCode <- waitForProcess procHandle
stdOut <- hGetContents hout
stdErr <- hGetContents herr
if exitCode /= ExitSuccess
then return $ Just $ concat [stdOut, stdErr]
else return $ Nothing
Upvotes: 3
Views: 681
Reputation: 452
Well, I am pretty sure I figured it out. Instead of using "fancy" stuff, I fell back to good ol' recursion.
runCommands :: [String] -> FilePath -> IO (Maybe String)
runCommands [] _ = return Nothing
runCommands (command:rest) curDir = do
result <- execCommand command curDir
case result of
Nothing -> runCommands rest curDir
Just err -> return $ Just err
Upvotes: 1
Reputation: 1312
Well, this might solve your problem:
{-# LANGUAGE FlexibleInstances #-}
import System.IO
import System.Process
import System.Exit
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
createCommand :: CmdSpec -> FilePath -> CreateProcess
createCommand (ShellCommand command) curDir =
(shell command){std_out = CreatePipe, std_err = CreatePipe, cwd = Just curDir}
createCommand (RawCommand command arguments) curDir =
(proc command arguments){std_out = CreatePipe, std_err = CreatePipe, cwd = Just curDir}
execCommand :: CmdSpec -> FilePath -> IO ()
execCommand command curDir = do
(_, Just hout, Just herr, procHandle) <- createProcess $ createCommand command curDir
exitCode <- waitForProcess procHandle
when (exitCode /= ExitSuccess) $ do
stdOut <- hGetContents hout
stdErr <- hGetContents herr
throwIO $ stdOut ++ stdErr
instance Exception String
execList :: [(CmdSpec, FilePath)] -> MaybeT IO String
execList xs = do
out <- liftIO $ try $ mapM_ (uncurry execCommand) xs
case out of
Left c -> return c
Right _ -> mzero
Notice that this uses FlexibleInstances. This was required for making String an instance of the Exception typeclass (the problem lies in the fact that String = [Char]). You could remove the extension by creating a new type which encloses a string and making it an instance of Exception.
Upvotes: 2
Reputation: 153102
You can use mapM_
, which has the type
mapM_ :: ( (CmdSpec, FilePath) -> ExceptT String IO ())
-> [(CmdSpec, FilePath)] -> ExceptT String IO ()
and appropriate short-circuiting behavior.
Upvotes: 0