diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,4 +1,10 @@
-# Revision history for simple-cmd
+# Version history for simple-cmd
+
+## 0.2.3 (2020-12-20)
+- most commands now use removeTrailingNewline
+- cmdFull: wrapper of readProcessWithExitCode
+- cmdStderrToStdoutIn: like cmdStderrToStdout but reads a String
+- pipe3 connects 3 commands
 
 ## 0.2.2 (2020-06-17)
 - grep: no longer errors for no match
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Build Status](https://travis-ci.org/juhp/simple-cmd.png)](https://travis-ci.org/juhp/simple-cmd)
+[![Build Status](https://travis-ci.org/juhp/simple-cmd.png)](https://travis-ci.com/juhp/simple-cmd)
 [![Hackage](http://img.shields.io/hackage/v/simple-cmd.png)](http://hackage.haskell.org/package/simple-cmd)
 [![Stackage LTS](http://stackage.org/package/simple-cmd/badge/lts)](http://stackage.org/lts/package/simple-cmd)
 [![Stackage Nightly](http://stackage.org/package/simple-cmd/badge/nightly)](http://stackage.org/nightly/package/simple-cmd)
diff --git a/SimpleCmd.hs b/SimpleCmd.hs
--- a/SimpleCmd.hs
+++ b/SimpleCmd.hs
@@ -32,6 +32,7 @@
   cmdIgnoreErr, {- badly named -}
   cmdLines,
   cmdMaybe,
+  cmdFull,
   cmdLog, cmdlog {-TODO: remove for 0.3 -},
   cmdN,
   cmdQuiet,
@@ -40,6 +41,7 @@
   cmdStdErr,
   cmdTry_,
   cmdStderrToStdout,
+  cmdStderrToStdoutIn,
   error',
   egrep_, grep, grep_,
   ifM,
@@ -52,7 +54,7 @@
   warning,
   PipeCommand,
   pipe, pipe_, pipeBool,
-  pipe3_, pipeFile_,
+  pipe3, pipe3_, pipeFile_,
   whenM,
   (+-+)) where
 
@@ -67,8 +69,8 @@
 
 import System.Directory (findExecutable)
 import System.Exit (ExitCode (..))
-import System.IO (hGetContents, hPutStrLn, IOMode(ReadMode), stderr, stdout,
-                  withFile)
+import System.IO (hGetContents, hPutStr, hPutStrLn, IOMode(ReadMode),
+                  stderr, stdout, withFile)
 import System.Posix.User (getEffectiveUserID)
 import System.Process (createProcess, proc, ProcessHandle, rawSystem, readProcess,
                        readProcessWithExitCode, runProcess, showCommandForUser,
@@ -124,10 +126,8 @@
 -- | @cmdMaybe c args@ runs a command, maybe returning output if it succeeds
 cmdMaybe :: String -> [String] -> IO (Maybe String)
 cmdMaybe c args = do
-  (ret, out, _err) <- readProcessWithExitCode c args ""
-  case ret of
-    ExitSuccess -> return $ Just $ removeTrailingNewline out
-    ExitFailure _ -> return Nothing
+  (ok, out, _err) <- cmdFull c args ""
+  return $ if ok then Just out else Nothing
 
 -- | @cmdLines c args@ runs a command, and returns list of stdout lines
 --
@@ -180,8 +180,8 @@
 -- | @cmdStdErr c args@ runs command in a process, returning stdout and stderr
 cmdStdErr :: String -> [String] -> IO (String, String)
 cmdStdErr c args = do
-  (_ret, out, err) <- readProcessWithExitCode c args ""
-  return (removeTrailingNewline out, removeTrailingNewline err)
+  (_ok, out, err) <- cmdFull c args ""
+  return (out, err)
 
 -- -- | @cmdAssert msg c args@ runs command, if it fails output msg as error'.
 -- cmdAssert :: String -> String -> [String] -> IO ()
@@ -194,25 +194,38 @@
 -- | @cmdQuiet c args@ runs a command hiding stderr, if it succeeds returns stdout
 cmdQuiet :: String -> [String] -> IO String
 cmdQuiet c args = do
-  (ret, out, err) <- readProcessWithExitCode c args ""
-  case ret of
-    ExitSuccess -> return $removeTrailingNewline out
-    ExitFailure n -> error' $ quoteCmd c args +-+ "failed with status" +-+ show n ++ "\n" ++ err
+  (ok, out, err) <- cmdFull c args ""
+  return $ if ok
+    then out
+    else error' $ quoteCmd c args +-+ "failed with\n" ++ err
 
 -- | @cmdSilent c args@ runs a command hiding stdout: stderr is only output if it fails.
 cmdSilent :: String -> [String] -> IO ()
 cmdSilent c args = do
-  (ret, _, err) <- readProcessWithExitCode c args ""
-  case ret of
-    ExitSuccess -> return ()
-    ExitFailure n -> error' $ quoteCmd c args +-+ "failed with status" +-+ show n ++ "\n" ++ err
+  (ret, _, err) <- cmdFull c args ""
+  unless ret $
+    error' $ quoteCmd c args +-+ "failed with\n" ++ err
 
+-- -- | @cmdSilentIn c args inp@ is like @cmdSilent@ but additionally takes some stdin
+-- cmdSilentIn :: String -> [String] -> String -> IO ()
+-- cmdSilentIn c args inp = do
+--   (ret, _, err) <- cmdFull c args inp
+--   unless ret $
+--     error' $ quoteCmd c args +-+ "failed with:\n" ++ err
+
 -- | @cmdIgnoreErr c args inp@ runs a command with input, drops stderr, and return stdout
 cmdIgnoreErr :: String -> [String] -> String -> IO String
 cmdIgnoreErr c args input = do
-  (_ret, out, _err) <- readProcessWithExitCode c args input
+  (_ret, out, _err) <- cmdFull c args input
   return out
 
+-- | @cmdFull c args inp@ runs readProcessWithExitCode and converts the ExitCode to Bool
+-- Removes the last newline from stdout and stderr (like the other functions)
+cmdFull :: String -> [String] -> String -> IO (Bool, String, String)
+cmdFull c args input = do
+  (ret, out, err) <- readProcessWithExitCode c args input
+  return (ret == ExitSuccess, removeTrailingNewline out, removeTrailingNewline err)
+
 -- | @cmdTry_ c args@ runs the command if available
 --
 -- @since 0.2.1
@@ -227,12 +240,27 @@
 -- @since 0.2.2
 cmdStderrToStdout :: String -> [String] -> IO (ExitCode, String)
 cmdStderrToStdout c args = do
-  (_, Just hout, _, p) <- createProcess ((proc c args) {std_out = CreatePipe,
-                                                      std_err = UseHandle stdout})
+  (_ , Just hout, _, p) <- createProcess ((proc c args)
+                                          {std_out = CreatePipe,
+                                           std_err = UseHandle stdout})
   ret <- waitForProcess p
   out <- hGetContents hout
-  return (ret,out)
+  return (ret, removeTrailingNewline out)
 
+-- | Redirect stderr to stdout, ie with interleaved output
+--
+-- @since 0.2.3
+cmdStderrToStdoutIn :: String -> [String] -> String -> IO (Bool, String)
+cmdStderrToStdoutIn c args inp = do
+  (Just hin, Just hout, _, p) <- createProcess ((proc c args)
+                                          {std_in  = CreatePipe,
+                                           std_out = CreatePipe,
+                                           std_err = UseHandle stdout})
+  hPutStr hin inp
+  ret <- waitForProcess p
+  out <- hGetContents hout
+  return (ret == ExitSuccess, removeTrailingNewline out)
+
 -- | @grep pat file@ greps pattern in file, and returns list of matches
 --
 -- @since 0.1.2 (fixed not to error in 0.2.2)
@@ -333,7 +361,7 @@
       out <- hGetContents ho2
       void $ waitForProcess p1
       void $ waitForProcess p2
-      return out
+      return $ removeTrailingNewline out
 
 -- | Pipe two commands without returning anything
 --
@@ -359,8 +387,22 @@
       void $ waitForProcess p1
       return p2
 
--- | Pipe 3 commands, no returning anything
+-- | Pipe 3 commands, returning stdout
 --
+-- @since 0.2.3
+pipe3 :: PipeCommand -> PipeCommand -> PipeCommand -> IO String
+pipe3 (c1,a1) (c2,a2) (c3,a3) =
+  withCreateProcess ((proc c1 a1) { std_out = CreatePipe }) $
+  \ _hi1 (Just ho1) _he1 p1 ->
+    withCreateProcess ((proc c2 a2) {std_in = UseHandle ho1, std_out = CreatePipe}) $
+    \ _hi2 (Just ho2) _he2 p2 -> do
+      (_, Just ho3, _, p3) <- createProcess ((proc c3 a3) {std_in = UseHandle ho2, std_out = CreatePipe})
+      out <- hGetContents ho3
+      forM_ [p1,p2,p3] waitForProcess
+      return $ removeTrailingNewline out
+
+-- | Pipe 3 commands, not returning anything
+--
 -- @since 0.2.0
 pipe3_ :: PipeCommand -> PipeCommand -> PipeCommand -> IO ()
 pipe3_ (c1,a1) (c2,a2) (c3,a3) =
@@ -369,9 +411,7 @@
     withCreateProcess ((proc c2 a2) {std_in = UseHandle ho1, std_out = CreatePipe}) $
     \ _hi2 ho2 _he2 p2 -> do
       p3 <- runProcess c3 a3 Nothing Nothing ho2 Nothing Nothing
-      void $ waitForProcess p1
-      void $ waitForProcess p2
-      void $ waitForProcess p3
+      forM_ [p1,p2,p3] waitForProcess
 
 -- | Pipe a file to the first of a pipe of commands
 --
diff --git a/SimpleCmd/Git.hs b/SimpleCmd/Git.hs
--- a/SimpleCmd/Git.hs
+++ b/SimpleCmd/Git.hs
@@ -35,6 +35,8 @@
   cmd_ "git" (c:args)
 
 -- | @gitBool c args@ runs git command and return result
+--
+-- @since 0.2.2
 gitBool :: String -- ^ git command
         -> [String] -- ^ arguments
         -> IO Bool -- ^ result
diff --git a/simple-cmd.cabal b/simple-cmd.cabal
--- a/simple-cmd.cabal
+++ b/simple-cmd.cabal
@@ -1,5 +1,5 @@
 name:                simple-cmd
-version:             0.2.2
+version:             0.2.3
 synopsis:            Simple String-based process commands
 description:
             Simple wrappers over System.Process
