diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -13,10 +13,12 @@
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE QuasiQuotes #-}
 module Readme (test) where
 
 import Shh
 
+import System.Environment
 import Control.Concurrent.Async
 import Prelude hiding (head)
 import Test.Tasty
@@ -26,6 +28,7 @@
 import qualified Data.ByteString.Lazy.Char8 as Char8
 import Data.List (nub)
 import Data.Char
+import PyF
 
 load SearchPath ["echo", "base64", "cat", "head", "sleep", "mktemp", "ls", "wc", "find", "tr", "users", "sha256sum", "false", "true"]
 
@@ -178,6 +181,18 @@
 might introduce a more succinct globbing feature, though it will always be
 explicit, and thus always more verbose than most other shells.
 
+## String Interpolation/Expansion/Substitution
+
+String interpolation, much like globbing, is left to an external library.
+I lightweight, zero-dependency solution is to use [PyF](https://hackage.haskell.org/package/PyF),
+which, since 0.10.0.1, has no dependencies other than ones included with GHC.
+
+```haskell
+greet =  do
+    user <- getEnv "USER"
+    echo [fmt|Hello, {user}!|]
+```
+
 ## Usage
 
 Some of the features in Shh require that you use the threaded runtime.
@@ -191,7 +206,7 @@
     {-# LANGUAGE ExtendedDefaultRules #-}
     $(loadEnv SearchPath)
 
-You now have all your executables available as simple to Haskell
+You now have all your executables available as simple Haskell
 functions. If you don't want to load your entire environment you
 can load specific commands directly:
 
@@ -223,7 +238,7 @@
 
 ```bash
 cabal new-install --lib shh
-cabal new-install --lib ssh-extras
+cabal new-install --lib shh-extras
 ```
 
 The `shh` binary will look in your `$SHH_DIR` (defaults to `$HOME/.shh`) for
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,2 +1,2 @@
-import Distribution.Simple
-main = defaultMain
+import Distribution.Extra.Doctest
+main = defaultMainWithDoctests "shh-tests"
diff --git a/app/Example.hs b/app/Example.hs
--- a/app/Example.hs
+++ b/app/Example.hs
@@ -12,7 +12,7 @@
 import Control.Concurrent.Async
 
 -- Load everything...
--- $(loadEnv)
+-- $(loadEnv SearchPath)
 
 -- OR --
 
diff --git a/app/shh-app.hs b/app/shh-app.hs
--- a/app/shh-app.hs
+++ b/app/shh-app.hs
@@ -111,7 +111,7 @@
             shellMod <- getModificationTime "Shell.hs"
             hiMod    <- getModificationTime "Shell.hi"
             pure (shellMod > hiMod)
-            ) (\e -> if (isDoesNotExistError e) then pure True else throwIO e)
+            ) (\e -> if isDoesNotExistError e then pure True else throwIO e)
         when (outdated || pathDiff) $ do
             putStrLn "Rebuilding Shell.hs..."
             writeFile "paths" cp
diff --git a/shh.cabal b/shh.cabal
--- a/shh.cabal
+++ b/shh.cabal
@@ -1,5 +1,5 @@
 name:                shh
-version:             0.7.2.0
+version:             0.7.2.1
 synopsis:            Simple shell scripting from Haskell
 description:         Provides a shell scripting environment for Haskell. It
                      helps you use external binaries, and allows you to
@@ -11,10 +11,16 @@
 maintainer:          lukec@themk.net
 copyright:           (c) 2018 - 2021 Luke Clifton
 category:            System
-build-type:          Simple
+build-type:          Custom
 extra-source-files:  ChangeLog.md, README.md
-cabal-version:       >=1.10
+cabal-version:       2.0
 
+custom-setup
+  setup-depends:
+      base
+    , Cabal
+    , cabal-doctest  >=1.0.9 && <1.1
+
 source-repository head
   type: git
   location: https://github.com/luke-clifton/shh
@@ -23,7 +29,7 @@
   exposed-modules:     Shh
                      , Shh.Internal
   build-depends:
-    base             >= 4.9 && < 4.16,
+    base             >= 4.9 && < 4.17,
     async            >= 2.2.1 && < 2.3,
     bytestring       >= 0.10.10 && < 0.12.0,
     deepseq          >= 1.4.3 && < 1.5,
@@ -33,7 +39,7 @@
     process          >= 1.6.3 && < 1.7,
     split            >= 0.2.3 && < 0.3,
     stringsearch     >= 0.3.6.6 && < 0.4,
-    template-haskell >= 2.13.0 && < 2.18,
+    template-haskell >= 2.13.0 && < 2.19,
     containers       >= 0.5.11 && < 0.7,
     unix             >= 2.7.2 && < 2.8,
     utf8-string
@@ -76,6 +82,7 @@
     directory,
     doctest,
     filepath,
+    PyF,
     tasty,
     tasty-quickcheck,
     tasty-hunit,
@@ -86,3 +93,5 @@
   main-is: Test.hs
   other-modules: Readme
   type: exitcode-stdio-1.0
+  other-modules: Build_doctests
+  autogen-modules: Build_doctests
diff --git a/src/Shh.hs b/src/Shh.hs
--- a/src/Shh.hs
+++ b/src/Shh.hs
@@ -53,6 +53,7 @@
     , captureEndBy
     , captureEndBy0
     , captureLines
+    , captureWords
     -- | == Piping and Redirection
     , (|>)
     , (|!>)
diff --git a/src/Shh/Internal.hs b/src/Shh/Internal.hs
--- a/src/Shh/Internal.hs
+++ b/src/Shh/Internal.hs
@@ -35,7 +35,6 @@
 import qualified Data.ByteString.Lazy.Search as Search
 import Data.ByteString.Lazy.UTF8 (toString)
 import Data.Char (isLower, isSpace, isAlphaNum, ord)
-import Data.List (intercalate)
 import qualified Data.List.Split as Split
 import qualified Data.Map as Map
 import Data.Maybe (isJust)
@@ -48,7 +47,6 @@
 import GHC.IO.Handle hiding (hGetContents)
 import GHC.IO.Handle.Internals
 import GHC.IO.Handle.Types
-import GHC.IO.Handle.Types (Handle(..))
 import GHC.Stack
 import Language.Haskell.TH
 import qualified System.Directory as Dir
@@ -105,7 +103,7 @@
     show f = concat $
         [ "Command `"
         ]
-        ++ [intercalate " " (toString (failureProg f) : map show (failureArgs f))]
+        ++ [unwords (toString (failureProg f) : map show (failureArgs f))]
         ++
         [ "` failed [exit "
         , show (failureCode f)
@@ -150,7 +148,7 @@
         concurrently a' b'
 
 
--- | Use this to send the output of on process into the input of another.
+-- | Use this to send the output of one process into the input of another.
 -- This is just like a shell's `|` operator.
 --
 -- The result is polymorphic in its output, and can result in either
@@ -170,7 +168,7 @@
 infixl 1 |>
 
 
--- | Similar to `|!>` except that it connects stderr to stdin of the
+-- | Similar to `|>` except that it connects stderr to stdin of the
 -- next process in the chain.
 --
 -- NB: The next command to be `|>` on will recapture the stdout of
@@ -440,7 +438,7 @@
 instance Shell Proc where
     runProc = id
 
--- | Run's a `Proc` in `IO`. Like `runProc`, but you get to choose the handles.
+-- | Runs a `Proc` in `IO`. Like `runProc`, but you get to choose the handles.
 -- This is UNSAFE to expose externally, because there are restrictions on what
 -- the Handle can be. Within shh, we never call `runProc'` with invalid handles,
 -- so we ignore that corner case (see `hDup`).
diff --git a/test/Readme.lhs b/test/Readme.lhs
--- a/test/Readme.lhs
+++ b/test/Readme.lhs
@@ -13,10 +13,12 @@
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE QuasiQuotes #-}
 module Readme (test) where
 
 import Shh
 
+import System.Environment
 import Control.Concurrent.Async
 import Prelude hiding (head)
 import Test.Tasty
@@ -26,6 +28,7 @@
 import qualified Data.ByteString.Lazy.Char8 as Char8
 import Data.List (nub)
 import Data.Char
+import PyF
 
 load SearchPath ["echo", "base64", "cat", "head", "sleep", "mktemp", "ls", "wc", "find", "tr", "users", "sha256sum", "false", "true"]
 
@@ -178,6 +181,18 @@
 might introduce a more succinct globbing feature, though it will always be
 explicit, and thus always more verbose than most other shells.
 
+## String Interpolation/Expansion/Substitution
+
+String interpolation, much like globbing, is left to an external library.
+I lightweight, zero-dependency solution is to use [PyF](https://hackage.haskell.org/package/PyF),
+which, since 0.10.0.1, has no dependencies other than ones included with GHC.
+
+```haskell
+greet =  do
+    user <- getEnv "USER"
+    echo [fmt|Hello, {user}!|]
+```
+
 ## Usage
 
 Some of the features in Shh require that you use the threaded runtime.
@@ -191,7 +206,7 @@
     {-# LANGUAGE ExtendedDefaultRules #-}
     $(loadEnv SearchPath)
 
-You now have all your executables available as simple to Haskell
+You now have all your executables available as simple Haskell
 functions. If you don't want to load your entire environment you
 can load specific commands directly:
 
@@ -223,7 +238,7 @@
 
 ```bash
 cabal new-install --lib shh
-cabal new-install --lib ssh-extras
+cabal new-install --lib shh-extras
 ```
 
 The `shh` binary will look in your `$SHH_DIR` (defaults to `$HOME/.shh`) for
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -20,7 +20,9 @@
 import Control.Concurrent.Async
 import System.FilePath (takeFileName)
 import System.IO
+import System.Environment
 
+import Build_doctests (flags, pkgs, module_sources)
 import Readme
 
 load SearchPath
@@ -35,12 +37,8 @@
     putStrLn " failures, please check that it's not because"
     putStrLn " they are missing."
     putStrLn "################################################"
-    doctest ["--fast", "-isrc", "src/Shh/Internal.hs"
-            , "-package", "async"
-            , "-package", "stringsearch"
-            , "-package", "utf8-string"
-            , "-package", "split"
-            ]
+    unsetEnv "GHC_ENVIRONMENT"
+    doctest $ flags ++ pkgs ++ module_sources
     defaultMain tests
 
 tests :: TestTree
@@ -238,16 +236,16 @@
         s <- writeOutput "üか" |> cat |> capture
         toString s @?= "üか"
     , testCase "tryFailure capture 1" $ do
-        s <- (tryFailure (false |> capture)) |> capture
+        s <- tryFailure (false |> capture) |> capture
         s @?= ""
     , testCase "tryFailure capture 2" $ do
-        s <- (tryFailure (false |> capture)) |!> capture
+        s <- tryFailure (false |> capture) |!> capture
         s @?= ""
     , testCase "tryFailure capture 3" $ do
-        s <- (tryFailure (false |!> capture)) |> capture
+        s <- tryFailure (false |!> capture) |> capture
         s @?= ""
     , testCase "tryFailure capture 4" $ do
-        s <- (tryFailure (false |!> capture)) |!> capture
+        s <- tryFailure (false |!> capture) |!> capture
         s @?= ""
     , testCase "pipeErr" $ do
         s <- writeOutput "abcd" |> capture `pipeErr` capture
