diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,6 @@
 # Revision history for shh
 
-## 0.7.1.4 -- 2021-06-30
+## 0.7.1.4 -- 2021-07-01
 
 * Fix a bug where `|>` was too strict, causing SIGPIPE
   to be triggered (ResourceVanished).
diff --git a/shh.cabal b/shh.cabal
--- a/shh.cabal
+++ b/shh.cabal
@@ -1,5 +1,5 @@
 name:                shh
-version:             0.7.1.4
+version:             0.7.2.0
 synopsis:            Simple shell scripting from Haskell
 description:         Provides a shell scripting environment for Haskell. It
                      helps you use external binaries, and allows you to
@@ -9,7 +9,7 @@
 license-file:        LICENSE
 author:              Luke Clifton
 maintainer:          lukec@themk.net
-copyright:           (c) 2018, 2019 Luke Clifton
+copyright:           (c) 2018 - 2021 Luke Clifton
 category:            System
 build-type:          Simple
 extra-source-files:  ChangeLog.md, README.md
@@ -25,7 +25,7 @@
   build-depends:
     base             >= 4.9 && < 4.16,
     async            >= 2.2.1 && < 2.3,
-    bytestring,
+    bytestring       >= 0.10.10 && < 0.12.0,
     deepseq          >= 1.4.3 && < 1.5,
     directory        >= 1.3.1 && < 1.4,
     filepath         >= 1.4.2 && < 1.5,
@@ -70,7 +70,6 @@
   ghc-options: -threaded -with-rtsopts=-N -pgmL markdown-unlit
   default-language: Haskell2010
   build-depends:
-    markdown-unlit,
     base >=4.9,
     async,
     bytestring,
@@ -83,6 +82,7 @@
     shh,
     utf8-string
   hs-source-dirs: test
+  build-tool-depends: markdown-unlit:markdown-unlit
   main-is: Test.hs
   other-modules: Readme
   type: exitcode-stdio-1.0
diff --git a/src/Shh.hs b/src/Shh.hs
--- a/src/Shh.hs
+++ b/src/Shh.hs
@@ -14,6 +14,11 @@
     -- to create these.
     , exe
     , mkProc
+    , mkProcWith
+    , ProcOptions()
+    , defaultProcOptions
+    , delegateCtlc
+    , closeFds
     , mkProc'
     , Proc()
     -- | === "Native" Processes (Lazy)
diff --git a/src/Shh/Internal.hs b/src/Shh/Internal.hs
--- a/src/Shh/Internal.hs
+++ b/src/Shh/Internal.hs
@@ -30,7 +30,7 @@
 import qualified Data.ByteString.Unsafe as ByteString
 import Data.ByteString.Lazy (ByteString, hGetContents, toStrict, fromStrict)
 import qualified Data.ByteString.Lazy as BS
-import Data.ByteString.Lazy.Builder.ASCII
+import Data.ByteString.Builder
 import qualified Data.ByteString.Lazy.Char8 as BC8
 import qualified Data.ByteString.Lazy.Search as Search
 import Data.ByteString.Lazy.UTF8 (toString)
@@ -136,8 +136,8 @@
 pipe (Proc a) (Proc b) = buildProc $ \i o e ->
     withPipe $ \r w -> do
         let
-            a' = a i w e `finally` (hClose w)
-            b' = b r o e `finally` (hClose r)
+            a' = a i w e `finally` hClose w
+            b' = b r o e `finally` hClose r
         concurrently a' b'
 
 -- | Like @`pipe`@, but plumbs stderr. See the warning in @`pipe`@.
@@ -145,8 +145,8 @@
 pipeErr (Proc a) (Proc b) = buildProc $ \i o e -> do
     withPipe $ \r w -> do
         let
-            a' = a i o w `finally` (hClose w)
-            b' = b r o e `finally` (hClose r)
+            a' = a i o w `finally` hClose w
+            b' = b r o e `finally` hClose r
         concurrently a' b'
 
 
@@ -166,9 +166,7 @@
 -- >>> echo "Hello" |> wc
 --       1       1       6
 (|>) :: Shell f => Proc a -> Proc b -> f b
-a |> b = runProc $ do
-    v <- fmap snd (a `pipe` b)
-    pure v
+a |> b = runProc $ fmap snd (a `pipe` b)
 infixl 1 |>
 
 
@@ -185,9 +183,7 @@
 -- Ignored
 -- 0
 (|!>) :: Shell f => Proc a -> Proc b -> f b
-a |!> b = runProc $ do
-    v <- fmap snd (a `pipeErr` b)
-    pure v
+a |!> b = runProc $ fmap snd (a `pipeErr` b)
 infixl 1 |!>
 
 -- | Things that can be converted to a @`FilePath`@.
@@ -238,7 +234,7 @@
 -- >>> echo "Shh" &!> StdOut
 -- Shh
 (&!>) :: Shell f => Proc a -> Stream -> f a
-p &!> StdErr = runProc $ p
+p &!> StdErr = runProc p
 (Proc f) &!> StdOut = buildProc $ \i o _ -> f i o o
 (Proc f) &!> (Truncate path) = buildProc $ \i o _ -> do
     path' <- toFilePath path
@@ -256,9 +252,9 @@
     -- real ones.
     withDuplicates i o e $ \i' o' e' -> do
         (f i' o' e' >>= C.evaluate . force)
-            `finally` (hClose i')
-            `finally` (hClose o')
-            `finally` (hClose e')
+            `finally` hClose i'
+            `finally` hClose o'
+            `finally` hClose e'
 
     where
         -- The resource vanished error only occurs when upstream pipe closes.
@@ -429,8 +425,7 @@
 
     f <*> a = do
         f' <- f
-        a' <- a
-        pure (f' a')
+        f' <$> a
         
 instance Monad Proc where
     (Proc a) >>= f = buildProc $ \i o e -> do
@@ -466,8 +461,26 @@
 -- | Create a `Proc` from a command and a list of arguments.
 -- The boolean represents whether we should delegate control-c
 -- or not. Most uses of @`mkProc'`@ in Shh do not delegate control-c.
+{-# DEPRECATED mkProc' "Use mkProcWith instead" #-}
 mkProc' :: HasCallStack => Bool -> ByteString -> [ByteString] -> Proc ()
-mkProc' delegate cmd args = Proc $ \i o e -> do
+mkProc' delegate = mkProcWith defaultProcOptions { delegateCtlc = delegate }
+
+-- | Options for making processes.
+data ProcOptions = ProcOptions
+    { delegateCtlc :: Bool -- ^ Delegate control-c handling to the child.
+    , closeFds     :: Bool -- ^ Close file descriptors before @exec@ing.
+    }
+
+-- | Default ProcOptions as used by most of this library.
+defaultProcOptions :: ProcOptions
+defaultProcOptions = ProcOptions
+    { delegateCtlc = True
+    , closeFds     = True
+    }
+
+-- | Create a `Proc` with custom options.
+mkProcWith :: HasCallStack => ProcOptions -> ByteString -> [ByteString] -> Proc ()
+mkProcWith options cmd args = Proc $ \i o e -> do
     cmd' <- toFilePath cmd
     args' <- mapM toFilePath args
     bracket
@@ -475,8 +488,8 @@
             { std_in = UseHandle i
             , std_out = UseHandle o
             , std_err = UseHandle e
-            , close_fds = True
-            , delegate_ctlc = delegate
+            , close_fds = closeFds options
+            , delegate_ctlc = delegateCtlc options
             }
         )
         (\(_,_,_,ph) -> terminateProcess ph >> waitForProcess ph)
@@ -485,7 +498,7 @@
 -- | Create a `Proc` from a command and a list of arguments. Does not delegate
 -- control-c handling.
 mkProc :: HasCallStack => ByteString -> [ByteString] -> Proc ()
-mkProc = mkProc' False
+mkProc = mkProcWith defaultProcOptions
 
 -- | A special `Proc` which captures its stdin and presents it as a `ByteString`
 -- to Haskell.
@@ -596,7 +609,7 @@
 -- | Apply a function that translates non-0 exit codes to results. Any code
 -- that returns a @Nothing@ will be thrown as a @`Failure`@.
 translateCode' :: Shell m => (Int -> Maybe b) -> Proc a -> m (Either b a)
-translateCode' f p = tryFailureJust (f . failureCode) p
+translateCode' f = tryFailureJust (f . failureCode)
 
 -- | Apply a function to non-0 exit codes to extract a result. If @Nothing@
 -- is produced, the @`Failure`@ is thrown.
@@ -777,13 +790,13 @@
 rawExe :: String -> String -> Q [Dec]
 rawExe fnName executable = do
     let
-        name = mkName $ fnName
+        name = mkName fnName
         impl = valD (varP name) (normalB [|
             withFrozenCallStack $ exe executable
             |]) []
         typ = SigD name (ConT ''Cmd)
     i <- impl
-    return $ [typ,i]
+    return [typ,i]
 
 -- | @$(loadExeAs ref fnName executable)@ defines a function called @fnName@
 -- which executes the path in @executable@. If @executable@ is an absolute path
@@ -873,7 +886,7 @@
 loadAnnotated :: ExecReference -> (String -> String) -> [FilePath] -> Q [Dec]
 loadAnnotated ref f bins = do
     let pairs = zip (map f bins) bins
-    ds <- fmap join $ mapM (uncurry (loadExeAs ref)) pairs
+    ds <- join <$> mapM (uncurry (loadExeAs ref)) pairs
     d <- valD (varP (mkName "missingExecutables")) (normalB [|
                 filterM (fmap not . checkExecutable) bins
             |]) []
@@ -1007,7 +1020,7 @@
 
 -- | Bracket a @`hDup`@
 withDuplicate :: Handle -> (Handle -> IO a) -> IO a
-withDuplicate h f = bracket (hDup h) hClose f
+withDuplicate h = bracket (hDup h) hClose
 
 -- | Bracket three @`hDup`@s
 withDuplicates :: Handle -> Handle -> Handle -> (Handle -> Handle -> Handle -> IO a) -> IO a
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -35,7 +35,12 @@
     putStrLn " failures, please check that it's not because"
     putStrLn " they are missing."
     putStrLn "################################################"
-    doctest ["--fast", "-isrc", "src/Shh/Internal.hs"]
+    doctest ["--fast", "-isrc", "src/Shh/Internal.hs"
+            , "-package", "async"
+            , "-package", "stringsearch"
+            , "-package", "utf8-string"
+            , "-package", "split"
+            ]
     defaultMain tests
 
 tests :: TestTree
