diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,10 +1,17 @@
 # Revision history for simple-cmd
 
-## 0.1.4
+## 0.2.0 (2019-06-03)
+- add warning command
+- API change: sudo and sudo_
+- add shellBool
+- add pipe, pipe_, pipeBool, pipeFile_, pipe3
+- quoteCmd uses showCommandForUser
+
+## 0.1.4 (2019-04-08)
 - export error'
 - add cmdLog (deprecates cmdlog)
 
-## 0.1.3.1
+## 0.1.3.1 (2019-03-15)
 - sudo: ignored for root or when no sudo installed
 
 ## 0.1.3 -- 2019-02-20
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@
 outputs to stdout. For example
 
 ```haskell
+cmd_ "echo" ["Hello"]
 cmd_ "git" ["clone", url]
 ```
 This can shortened to `git_ "clone" [url]`.
@@ -25,15 +26,24 @@
 ```haskell
 cmd :: String -> [String] -> IO String
 ```
-returns stdout as a `String`.
+returns stdout as a `String`. eg
 
+```haskell
+date <- cmd "date" []
+```
+
 There are also `cmdBool`, `cmdMaybe`, `cmdList`, `shell`, and others.
 
+Simple pipes are also supported:
+```haskell
+pipe_ ("echo",["hello"]) ("grep",["ello"])
+```
+
 Other examples:
 ```haskell
 gitBranch :: IO String
 grep_ pat file :: IO Bool
-sudo c args :: IO ()
+sudo_ c args :: IO ()
 ```
 
 See the library documentation for more details.
diff --git a/SimpleCmd.hs b/SimpleCmd.hs
--- a/SimpleCmd.hs
+++ b/SimpleCmd.hs
@@ -32,7 +32,7 @@
   cmdIgnoreErr,
   cmdLines,
   cmdMaybe,
-  cmdLog, cmdlog {-TODO: remove for 0.2 -},
+  cmdLog, cmdlog {-TODO: remove for 0.3 -},
   cmdN,
   cmdQuiet,
   cmdSilent,
@@ -43,23 +43,31 @@
   logMsg,
   removePrefix, removeStrictPrefix, removeSuffix,
   shell, shell_,
-  sudo,
+  shellBool,
+  sudo, sudo_,
+  warning,
+  PipeCommand,
+  pipe, pipe_, pipeBool,
+  pipe3_, pipeFile_,
   (+-+)) where
 
 #if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,8,0))
 #else
 import Control.Applicative ((<$>))
 #endif
-import Control.Monad (when)
+import Control.Monad
 
 import Data.List (stripPrefix)
 import Data.Maybe (isNothing, fromMaybe)
 
 import System.Directory (findExecutable)
 import System.Exit (ExitCode (..))
-import System.IO (hPutStrLn, stderr)
+import System.IO (hGetContents, hPutStrLn, IOMode(ReadMode), stderr, withFile)
 import System.Posix.User (getEffectiveUserID)
-import System.Process (readProcess, readProcessWithExitCode, rawSystem)
+import System.Process (createProcess, proc, ProcessHandle, rawSystem, readProcess,
+                       readProcessWithExitCode, runProcess, showCommandForUser,
+                       std_in, std_out, StdStream(CreatePipe, UseHandle),
+                       waitForProcess, withCreateProcess)
 
 removeTrailingNewline :: String -> String
 removeTrailingNewline "" = ""
@@ -69,7 +77,7 @@
   else str
 
 quoteCmd :: String -> [String] -> String
-quoteCmd c args = "'" ++ unwords (c:args) ++ "'"
+quoteCmd = showCommandForUser
 
 -- | Alias for errorWithoutStackTrace (for base >= 4.9)
 --
@@ -95,14 +103,18 @@
     ExitSuccess -> return ()
     ExitFailure n -> error' $ quoteCmd c args +-+ "failed with exit code" +-+ show n
 
--- | 'cmdBool c args' runs a command, and return Boolean status
-cmdBool :: String -> [String] -> IO Bool
-cmdBool c args = do
-  ret <- rawSystem c args
+boolWrapper :: IO ExitCode -> IO Bool
+boolWrapper pr = do
+  ret <- pr
   case ret of
     ExitSuccess -> return True
     ExitFailure _ -> return False
 
+-- | 'cmdBool c args' runs a command, and return Boolean status
+cmdBool :: String -> [String] -> IO Bool
+cmdBool c args =
+  boolWrapper (rawSystem c args)
+
 -- | 'cmdMaybe c args' runs a command, maybe returning output if it succeeds
 cmdMaybe :: String -> [String] -> IO (Maybe String)
 cmdMaybe c args = do
@@ -125,10 +137,17 @@
 shell :: String -> IO String
 shell cs = cmd "sh" ["-c", cs]
 
--- | 'shell_ c' runs a command string in a shell, output goes to stdout
+-- | 'shell_ cs' runs a command string in a shell, output goes to stdout
 shell_ :: String -> IO ()
-shell_ c = cmd_ "sh" ["-c", c]
+shell_ cs = cmd_ "sh" ["-c", cs]
 
+-- | 'shellBool cs' runs a command string in a shell, output goes to stdout
+--
+-- @since 0.2.0
+shellBool :: String -> IO Bool
+shellBool cs =
+  boolWrapper (rawSystem "sh" ["-c", cs])
+
 -- | 'cmdLog c args' logs a command with a datestamp
 --
 -- @since 0.1.4
@@ -137,7 +156,7 @@
   logMsg $ unwords $ c:args
   cmd_ c args
 
--- | 'cmdlog' deprecated alias for 'cmdLog' (to be removed in 0.2)
+-- | 'cmdlog' deprecated alias for 'cmdLog' (will be removed in 0.3)
 cmdlog :: String -> [String] -> IO ()
 cmdlog = cmdLog
 
@@ -206,19 +225,32 @@
 egrep_ pat file =
   cmdBool "grep" ["-q", "-e", pat, file]
 
--- | 'sudo c args' runs a command as sudo
+-- | 'sudo c args' runs a command as sudo returning stdout
+--
+-- Result type changed from IO () to IO String in 0.2.0
 sudo :: String -- ^ command
      -> [String] -- ^ arguments
+     -> IO String
+sudo = sudoInternal cmd
+
+-- | 'sudo_ c args' runs a command as sudo
+--
+-- @since 0.2.0
+sudo_ :: String -- ^ command
+     -> [String] -- ^ arguments
      -> IO ()
-sudo c args = do
+sudo_ = sudoInternal cmdLog
+
+sudoInternal :: (String -> [String] -> IO a) -> String -> [String] -> IO a
+sudoInternal exc c args = do
   uid <- getEffectiveUserID
   sd <- if uid == 0
     then return Nothing
     else findExecutable "sudo"
   let noSudo = isNothing sd
   when (uid /= 0 && noSudo) $
-    hPutStrLn stderr "'sudo' not found"
-  cmdLog (fromMaybe c sd) (if noSudo then args else c:args)
+    warning "'sudo' not found"
+  exc (fromMaybe c sd) (if noSudo then args else c:args)
 
 -- | Combine two strings with a single space
 infixr 4 +-+
@@ -249,3 +281,76 @@
   fromMaybe orig $ stripSuffix suffix orig
   where
     stripSuffix sf str = reverse <$> stripPrefix (reverse sf) (reverse str)
+
+-- | 'warning' outputs to stderr
+--
+-- @since 0.2.0
+warning :: String -> IO ()
+warning = hPutStrLn stderr
+
+type PipeCommand = (String,[String])
+
+-- | Return stdout from piping the output of one process to another
+--
+-- @since 0.2.0
+pipe :: PipeCommand -> PipeCommand -> IO String
+pipe (c1,args1) (c2,args2) =
+  withCreateProcess ((proc c1 args1) { std_out = CreatePipe }) $
+    \ _si (Just ho1) _se p1 -> do
+      (_, Just ho2, _, p2) <- createProcess ((proc c2 args2) {std_in = UseHandle ho1, std_out = CreatePipe})
+      out <- hGetContents ho2
+      void $ waitForProcess p1
+      void $ waitForProcess p2
+      return out
+
+-- | Pipe two commands without returning anything
+--
+-- @since 0.2.0
+pipe_ :: PipeCommand -> PipeCommand -> IO ()
+pipe_ (c1,args1) (c2,args2) =
+  void $ pipeInternal (c1,args1) (c2,args2) >>= waitForProcess
+
+-- | Bool result of piping of commands
+--
+-- @since 0.2.0
+pipeBool :: PipeCommand -> PipeCommand -> IO Bool
+pipeBool (c1,args1) (c2,args2) =
+  boolWrapper $ pipeInternal (c1,args1) (c2,args2) >>= waitForProcess
+
+pipeInternal :: PipeCommand -> PipeCommand -> IO ProcessHandle
+pipeInternal (c1,args1) (c2,args2) =
+  -- nicer with process-typed:
+  -- withProcess_ (setStdout createPipe proc1) $ \ p -> runProcess (setStdin (useHandleClose (getStdout p)) proc2)
+  withCreateProcess ((proc c1 args1) { std_out = CreatePipe }) $
+    \ _si so _se p1 -> do
+      p2 <- runProcess c2 args2 Nothing Nothing so Nothing Nothing
+      void $ waitForProcess p1
+      return p2
+
+-- | Pipe 3 commands, no returning anything
+--
+-- @since 0.2.0
+pipe3_ :: PipeCommand -> PipeCommand -> PipeCommand -> IO ()
+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 ho2 _he2 p2 -> do
+      p3 <- runProcess c3 a3 Nothing Nothing ho2 Nothing Nothing
+      void $ waitForProcess p1
+      void $ waitForProcess p2
+      void $ waitForProcess p3
+
+-- | Pipe a file to the first of a pipe of commands
+--
+-- @since 0.2.0
+pipeFile_ :: FilePath -> PipeCommand -> PipeCommand -> IO ()
+pipeFile_ infile (c1,a1) (c2,a2) =
+  withFile infile ReadMode $
+  \ hin ->
+    withCreateProcess ((proc c1 a1) { std_in = UseHandle hin, std_out = CreatePipe }) $
+    \ _si so _se p1 -> do
+      p2 <- runProcess c2 a2 Nothing Nothing so Nothing Nothing
+      void $ waitForProcess p1
+      void $ waitForProcess p2
+
diff --git a/simple-cmd.cabal b/simple-cmd.cabal
--- a/simple-cmd.cabal
+++ b/simple-cmd.cabal
@@ -1,10 +1,10 @@
 name:                simple-cmd
-version:             0.1.4
+version:             0.2.0
 synopsis:            Simple String-based process commands
 description:
-            Thin wrappers over System.Process (readProcess,
-            readProcessWithExitCode, and rawSystem). The idea is to provide
-            some simple common idioms for easy calling out to commands
+            Simple wrappers over System.Process
+            (readProcess, readProcessWithExitCode, rawSystem, and createProcess). 
+            The idea is to provide some common idioms for calling out to commands
             from programs.  For more advanced shell-scripting or streaming
             use turtle, shelly, command, etc.
 license:             BSD3
@@ -18,7 +18,7 @@
 extra-source-files:  README.md ChangeLog.md TODO
 tested-with:         GHC == 7.0.4, GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.4,
                      GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4,
-                     GHC == 8.6.4
+                     GHC == 8.6.5
 
 source-repository head
   type:     git
