diff --git a/qhs.cabal b/qhs.cabal
--- a/qhs.cabal
+++ b/qhs.cabal
@@ -1,6 +1,6 @@
 cabal-version:          3.0
 name:                   qhs
-version:                0.4.1
+version:                0.4.3
 category:               Console
 synopsis:               Command line tool qhs, SQL queries on CSV and TSV files.
 description:            This is a Haskell port of <https://github.com/harelba/q q command>.
@@ -35,10 +35,10 @@
                         Qhs.SQL
                         Qhs.SQLType
   build-depends:        bytestring >= 0.11 && < 0.13
-                      , containers >= 0.6 && < 0.7
+                      , containers >= 0.6 && < 0.9
                       , cryptonite >= 0.30 && < 0.31
                       , extra >= 1.7 && < 1.9
-                      , optparse-applicative >= 0.18 && < 0.19
+                      , optparse-applicative >= 0.18 && < 0.20
                       , simple-sql-parser >= 0.8 && < 0.9
                       , sqlite-simple >= 0.4 && < 0.5
                       , syb >= 0.7 && < 0.8
@@ -62,10 +62,12 @@
                         QhsSpec
                         SQLSpec
   build-depends:        qhs-lib
-                      , containers >= 0.6 && < 0.7
-                      , extra >= 1.7 && < 1.8
+                      , containers >= 0.6 && < 0.9
+                      , extra >= 1.7 && < 1.9
                       , hspec >= 2.11 && < 2.12
                       , process >= 1.6 && < 1.7
+  build-tool-depends:   hspec-discover:hspec-discover
+                      , qhs:qhs
 
 source-repository head
   type:     git
diff --git a/src/Qhs/CLI.hs b/src/Qhs/CLI.hs
--- a/src/Qhs/CLI.hs
+++ b/src/Qhs/CLI.hs
@@ -44,9 +44,15 @@
                             guard opts.tabDelimited *> Just "\t" <|>
                             guard opts.pipeDelimited *> Just "|" <|>
                             opts.delimiter
+      let escapeField s
+            | any needsEscape s = "\"" ++ concatMap escapeChar s ++ "\""
+            | otherwise = s
+            where needsEscape c = c == '"' || c == '\n' || c == '\r' || any (==c) outputDelimiter
+                  escapeChar '"' = "\"\""
+                  escapeChar c = [c]
       when opts.outputHeader $
-        putStrLn $ intercalate outputDelimiter cs
-      mapM_ (putStrLn . intercalate outputDelimiter . map show) rs
+        putStrLn $ intercalate outputDelimiter (map escapeField cs)
+      mapM_ (putStrLn . intercalate outputDelimiter . map (escapeField . show)) rs
     Left err -> do
       hPrint stderr err
       exitFailure
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -1,3 +1,3 @@
-resolver: lts-23.24
+resolver: lts-24.42
 extra-deps:
   - simple-sql-parser-0.8.0
diff --git a/stack.yaml.lock b/stack.yaml.lock
--- a/stack.yaml.lock
+++ b/stack.yaml.lock
@@ -5,15 +5,15 @@
 
 packages:
 - completed:
-    hackage: simple-sql-parser-0.8.0@sha256:5153612af09edda2af865e6dc1286d64ecea702541a1af824ac518268f0b5cb4,4417
+    hackage: simple-sql-parser-0.8.0@sha256:dedefe5ed8627d9f619714aa145429d8b5127218f86de575c3ee480f969ae952,4522
     pantry-tree:
-      sha256: fba7264bf9753b0c6abf40881e43b0b56e97052487c5ccbfd663892cdcfdfc13
+      sha256: a08dba51ab34387433be00ce15f9ef53ad195540453d40c1bbdc2d6c3764eba2
       size: 2898
   original:
     hackage: simple-sql-parser-0.8.0
 snapshots:
 - completed:
-    sha256: 400dfb2174640dd2f9f2ba7cbf9148699f2380ab64faa46f4be298a5d6205316
-    size: 684057
-    url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/23/24.yaml
-  original: lts-23.24
+    sha256: 85363ca92602ef9c8c3f6cfdb188efd3db3209ca3280eea4aad3b38b12a208a1
+    size: 729011
+    url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/42.yaml
+  original: lts-24.42
diff --git a/test/QhsSpec.hs b/test/QhsSpec.hs
--- a/test/QhsSpec.hs
+++ b/test/QhsSpec.hs
@@ -20,7 +20,9 @@
           "pipe_delimited", "pipe_delimited_output", "delimiters",
           "query_file", "empty_query", "empty_query_file", "file_spaces",
           "gzip", "gzip_stdin", "avg", "sum", "avg_sum", "seq",
-          "group", "group_sum", "concat", "join", "invalid"
+          "group", "group_sum", "concat", "join", "invalid",
+          "multiline_output", "escape_delimiter", "escape_custom_delimiter",
+          "escape_quotes", "escape_carriage_return"
           ]
 
     forM_ tests \test -> do
diff --git a/test/tests/escape_carriage_return.csv b/test/tests/escape_carriage_return.csv
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_carriage_return.csv
@@ -0,0 +1,2 @@
+a,b,c
+1,"helloworld",2
diff --git a/test/tests/escape_carriage_return.out b/test/tests/escape_carriage_return.out
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_carriage_return.out
@@ -0,0 +1,1 @@
+1 "helloworld" 2
diff --git a/test/tests/escape_carriage_return.sh b/test/tests/escape_carriage_return.sh
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_carriage_return.sh
@@ -0,0 +1,1 @@
+qhs -H "SELECT * FROM escape_carriage_return.csv"
diff --git a/test/tests/escape_custom_delimiter.csv b/test/tests/escape_custom_delimiter.csv
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_custom_delimiter.csv
@@ -0,0 +1,2 @@
+a|b|c
+1|"hello|world"|2
diff --git a/test/tests/escape_custom_delimiter.out b/test/tests/escape_custom_delimiter.out
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_custom_delimiter.out
@@ -0,0 +1,1 @@
+1|"hello|world"|2
diff --git a/test/tests/escape_custom_delimiter.sh b/test/tests/escape_custom_delimiter.sh
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_custom_delimiter.sh
@@ -0,0 +1,1 @@
+qhs -H -d '|' "SELECT * FROM escape_custom_delimiter.csv"
diff --git a/test/tests/escape_delimiter.csv b/test/tests/escape_delimiter.csv
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_delimiter.csv
@@ -0,0 +1,2 @@
+a,b,c
+1,"hello	world",2
diff --git a/test/tests/escape_delimiter.out b/test/tests/escape_delimiter.out
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_delimiter.out
@@ -0,0 +1,1 @@
+1	"hello	world"	2
diff --git a/test/tests/escape_delimiter.sh b/test/tests/escape_delimiter.sh
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_delimiter.sh
@@ -0,0 +1,1 @@
+qhs -H -T "SELECT * FROM escape_delimiter.csv"
diff --git a/test/tests/escape_quotes.csv b/test/tests/escape_quotes.csv
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_quotes.csv
@@ -0,0 +1,2 @@
+a,b,c
+1,"say ""hello""",2
diff --git a/test/tests/escape_quotes.out b/test/tests/escape_quotes.out
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_quotes.out
@@ -0,0 +1,1 @@
+1 "say ""hello""" 2
diff --git a/test/tests/escape_quotes.sh b/test/tests/escape_quotes.sh
new file mode 100644
--- /dev/null
+++ b/test/tests/escape_quotes.sh
@@ -0,0 +1,1 @@
+qhs -H "SELECT * FROM escape_quotes.csv"
diff --git a/test/tests/multiline_output.csv b/test/tests/multiline_output.csv
new file mode 100644
--- /dev/null
+++ b/test/tests/multiline_output.csv
@@ -0,0 +1,3 @@
+a,b,c
+1,"hello
+world",2
diff --git a/test/tests/multiline_output.out b/test/tests/multiline_output.out
new file mode 100644
--- /dev/null
+++ b/test/tests/multiline_output.out
@@ -0,0 +1,2 @@
+1 "hello
+world" 2
diff --git a/test/tests/multiline_output.sh b/test/tests/multiline_output.sh
new file mode 100644
--- /dev/null
+++ b/test/tests/multiline_output.sh
@@ -0,0 +1,1 @@
+qhs -H "SELECT * FROM multiline_output.csv"
