diff --git a/Control/Shell.hs b/Control/Shell.hs
--- a/Control/Shell.hs
+++ b/Control/Shell.hs
@@ -6,7 +6,7 @@
   , shell, shell_, exitString
 
     -- * Error handling and control flow
-  , (|>), capture, captureStdErr, capture2, stream, lift
+  , (|>), capture, captureStdErr, capture2, capture3, stream, lift
   , try, orElse, exit
   , Guard (..), guard, when, unless
 
diff --git a/Control/Shell/Base.hs b/Control/Shell/Base.hs
--- a/Control/Shell/Base.hs
+++ b/Control/Shell/Base.hs
@@ -7,7 +7,7 @@
   , MonadIO (..), shellEnv
   , shell_
   , stdin, echo, echo_, ask
-  , capture, captureStdErr, capture2, stream, lift
+  , capture, captureStdErr, capture2, capture3, stream, lift
   , takeEnvLock, releaseEnvLock, setShellEnv, joinResult, absPath
   ) where
 import qualified System.Process as Proc
@@ -16,6 +16,7 @@
 import qualified System.Directory as Dir
 import qualified System.Environment as Env
 import System.FilePath
+import Data.Either (either)
 import Data.IORef
 import Control.Monad.IO.Class
 import System.IO.Unsafe
@@ -115,6 +116,21 @@
     o <- IO.hGetContents ro
     e <- IO.hGetContents re
     return (o, e)
+
+-- | Perform the given computation and return its standard output and error,
+--   as well as its exit reason, in that order.
+capture3 :: Shell () -> Shell (String, String, ExitReason)
+capture3 m = do
+  env <- getEnv
+  (ro, wo) <- unsafeLiftIO Proc.createPipe
+  (re, we) <- unsafeLiftIO Proc.createPipe
+  res <- inEnv (env {envStdOut = wo, envStdErr = we}) $ try m
+  unsafeLiftIO $ do
+    IO.hClose wo
+    IO.hClose we
+    o <- IO.hGetContents ro
+    e <- IO.hGetContents re
+    return (o, e, either Failure (const Success) res)
 
 -- | Lift a pure function to a computation over standard input/output.
 --   Similar to 'interact'.
diff --git a/Control/Shell/Internal.hs b/Control/Shell/Internal.hs
--- a/Control/Shell/Internal.hs
+++ b/Control/Shell/Internal.hs
@@ -94,9 +94,10 @@
   ((stepenv, step) : steps) <- mkEnvs env p
   ma <- waitPids =<< mapM (uncurry (runStep True)) steps
   mb <- waitPids . (:[]) =<< runStep False stepenv step
-  case ma >> mb of
-    Just err -> pure $ Left err
-    _        -> pure $ Right ()
+  case (ma, mb) of
+    (Failure err, _) -> pure $ Left (Failure err)
+    (_, Failure err) -> pure $ Left (Failure err)
+    _                -> pure $ Right ()
   where
     except = \(Ex.SomeException e) -> pure $ Left (Failure (show e))
 runSh _ Done = do
@@ -177,23 +178,23 @@
 
 -- | Wait for all processes in the given list. If a process has failed, its
 --   error message is returned and the rest are killed.
-waitPids :: [Pid] -> IO (Maybe ExitReason)
+waitPids :: [Pid] -> IO ExitReason
 waitPids (PID cmd p : ps) = do
   exCode <- Proc.waitForProcess p
   case exCode of
     Exit.ExitFailure ec -> do
       mapM_ killPid ps
-      return . Just $ Failure $ "Command '" ++ cmd ++ "' failed with error "
-                              ++" code " ++ show ec
+      return . Failure $ concat
+        ["Command `", cmd, "' failed with error code ", show ec]
     _ -> do
       waitPids ps
 waitPids (TID v _ : ps) = do
   merr <- Conc.takeMVar v
   case merr of
-    Just e -> mapM_ killPid ps >> return (Just e)
+    Just e -> mapM_ killPid ps >> return e
     _      -> waitPids ps
 waitPids _ = do
-  return Nothing
+  return Success
 
 -- | Execute an external command. No globbing, escaping or other external shell
 --   magic is performed on either the command or arguments. The program's
diff --git a/shellmate.cabal b/shellmate.cabal
--- a/shellmate.cabal
+++ b/shellmate.cabal
@@ -1,5 +1,5 @@
 name:                shellmate
-version:             0.3.4
+version:             0.3.4.1
 synopsis:            Simple interface for shell scripting in Haskell.
 description:         Aims to simplify development of cross-platform shell scripts and similar things.
 homepage:            https://github.com/valderman/shellmate
