diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,8 +8,24 @@
 
 ## Unreleased
 
+## 0.0.2.0 - 2023-09-15
+
+- [#7](https://github.com/parsonsmatt/hotel-california/pull/7)
+    - Fixed escaping of shell commands. Previously, single quotes would not be
+      passed correctly, so a command like this:
+        ```
+        hotel exec -- cabal build --ghc-options='-Werror -ferror-spans'
+        ```
+      would get passed like `cabal ["build", "--ghc-options='-Werror'", "-ferror-spans'"]`.
+      This is now fixed, and it will be properly passed as `cabal ["build", "--ghc-options='-Werror -ferrorspans'"]`.
+    - CLI interface changed subtly - now, `hotel exec command [args]` will do a
+      process lookup for `command`. If you want to pass a shell script, instead
+      do:
+        ```
+        hotel exec --shell 'echo a && echo b || true'
+        ```
+
 ## 0.0.1.0 - 2023-09-12
 
 - Initial Release
 - Introduce `exec` functionality
-
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -10,7 +10,8 @@
 
 ```sh
 $ hotel exec --help
-Usage: hotel exec [-s|--span-name SPAN_NAME] SCRIPT [SCRIPT...]
+Usage: hotel exec [-s|--span-name SPAN_NAME]
+                  (COMMAND [ARGUMENT]... | --shell SCRIPT)
 
   Execute the given command with tracing enabled
 
@@ -18,9 +19,8 @@
   -h,--help                Show this help text
   -s,--span-name SPAN_NAME The name of the span that the program reports. By
                            default, this is the script you pass in.
-  SCRIPT                   The command to run, along with any arguments. Best to
-                           use -- before providing the script, otherwise it may
-                           pass arguments to `hotel` instead of to your script
+  --shell SCRIPT           Run an arbitrary shell script instead of running an
+                           executable command
 ```
 
 Currently, the program only looks in environment variables for configuration.
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -24,7 +24,7 @@
     info' :: Parser a -> String -> ParserInfo a
     info' p desc = info
         (helper <*> p)
-        (fullDesc <> progDesc desc)
+        (fullDesc <> progDesc desc <> noIntersperse)
 
     parser' :: Parser Command
     parser' =
@@ -48,7 +48,8 @@
 main :: IO ()
 main = do
     withGlobalTracing do
-        Command {..} <- execParser optionsParser
+        let parserPrefs = defaultPrefs{ prefMultiSuffix = "..." }
+        Command {..} <- customExecParser parserPrefs optionsParser
         case commandSubCommand of
             Exec execArgs ->
                 runExecArgs execArgs
diff --git a/hotel-california.cabal b/hotel-california.cabal
--- a/hotel-california.cabal
+++ b/hotel-california.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hotel-california
-version:        0.0.1.0
+version:        0.0.2.0
 description:    Please see the README on GitHub at <https://github.com/parsonsmatt/hotel-california#readme>
 homepage:       https://github.com/parsonsmatt/hotel-california#readme
 bug-reports:    https://github.com/parsonsmatt/hotel-california/issues
@@ -82,6 +82,7 @@
     , hs-opentelemetry-vendor-honeycomb
     , http-types
     , optparse-applicative
+    , posix-escape
     , text
     , time
     , typed-process
@@ -144,6 +145,7 @@
     , hs-opentelemetry-vendor-honeycomb
     , http-types
     , optparse-applicative
+    , posix-escape
     , text
     , time
     , typed-process
@@ -207,6 +209,7 @@
     , hs-opentelemetry-vendor-honeycomb
     , http-types
     , optparse-applicative
+    , posix-escape
     , text
     , time
     , typed-process
diff --git a/src/HotelCalifornia/Exec.hs b/src/HotelCalifornia/Exec.hs
--- a/src/HotelCalifornia/Exec.hs
+++ b/src/HotelCalifornia/Exec.hs
@@ -10,15 +10,43 @@
 import HotelCalifornia.Tracing
 import HotelCalifornia.Tracing.TraceParent
 import System.Environment (getEnvironment)
-import Options.Applicative
+import qualified System.Posix.Escape.Unicode as Escape
+import Options.Applicative hiding (command)
 import System.Exit
 import System.Process.Typed
 
+data Subprocess = Proc (NonEmpty String) | Shell String
+
+commandToString :: Subprocess -> String
+commandToString (Proc tokens) = Escape.escapeMany (NEL.toList tokens)
+commandToString (Shell line) = line
+
+commandToProcessConfig :: Subprocess -> ProcessConfig () () ()
+commandToProcessConfig (Proc (command :| args)) = proc command args
+commandToProcessConfig (Shell line) = shell line
+
 data ExecArgs = ExecArgs
-    { execArgsScript :: NonEmpty String
+    { execArgsSubprocess :: Subprocess
     , execArgsSpanName :: Maybe Text
     }
 
+parseProc :: Parser (NonEmpty String)
+parseProc = do
+    command <- argument str (metavar "COMMAND")
+    arguments <- many (argument str (metavar "ARGUMENT"))
+    return (command :| arguments)
+
+parseShell :: Parser String
+parseShell =
+    option str
+        (   metavar "SCRIPT"
+        <>  long "shell"
+        <>  help "Run an arbitrary shell script instead of running an executable command"
+        )
+
+parseSubprocess :: Parser Subprocess
+parseSubprocess = fmap Proc parseProc <|> fmap Shell parseShell
+
 parseExecArgs :: Parser ExecArgs
 parseExecArgs = do
     execArgsSpanName <- optional do
@@ -28,17 +56,12 @@
             , short 's'
             , help "The name of the span that the program reports. By default, this is the script you pass in."
             ]
-    execArgsScript1 <- argument str (metavar "SCRIPT" <> help "The command to run, along with any arguments. Best to use -- before providing the script, otherwise it may pass arguments to `hotel` instead of to your script")
-    execArgsScriptRest <- many $ argument str (metavar "SCRIPT...")
-    pure ExecArgs
-        { execArgsScript = execArgsScript1 :| execArgsScriptRest
-        , ..
-        }
+    execArgsSubprocess <- parseSubprocess
+    pure ExecArgs{..}
 
 runExecArgs :: ExecArgs -> IO ()
 runExecArgs ExecArgs {..} = do
-    let script =
-            unwords $ NEL.toList execArgsScript
+    let script = commandToString execArgsSubprocess
         spanName =
             fromMaybe (Text.pack script) execArgsSpanName
 
@@ -46,8 +69,9 @@
         newEnv <- spanContextToEnvironment span_
         fullEnv <- mappend newEnv <$> getEnvironment
 
-        let processConfig = shell $ unwords $ NEL.toList execArgsScript
-        exitCode <- runProcess $ setEnv fullEnv $ processConfig
+        let processConfig = commandToProcessConfig execArgsSubprocess
+
+        exitCode <- runProcess $ setEnv fullEnv processConfig
         case exitCode of
             ExitSuccess ->
                 pure ()
