diff --git a/executable/Main.hs b/executable/Main.hs
--- a/executable/Main.hs
+++ b/executable/Main.hs
@@ -54,7 +54,7 @@
     pipeline :: OptionParser (IO ()) }
 
 tools = external ++ internal
-external = [fof, cnf, uncnf, smt, monotonox, hornToUnit, inferTypes]
+external = [fof, cnf, uncnf, smt, monotonox, hornToUnit, inferTypes, cat]
 internal = [guessmodel, parse]
 
 fof =
@@ -129,3 +129,9 @@
     forAllFilesBox <*>
       (readProblemBox =>>=
        pure (const (return ())))
+
+cat =
+  Tool "cat" "Find a file and print it out verbatim." $
+    forAllFilesBox <*>
+      (readTPTPFileBox =>>=
+       writeFileBox)
diff --git a/jukebox.cabal b/jukebox.cabal
--- a/jukebox.cabal
+++ b/jukebox.cabal
@@ -1,6 +1,6 @@
 Name: jukebox
-Version: 0.4.4
-Cabal-version: >= 1.8
+Version: 0.4.5
+Cabal-version: >= 1.10
 Build-type: Simple
 Author: Nick Smallbone
 Maintainer: nicsma@chalmers.se
@@ -28,9 +28,10 @@
   location: https://github.com/nick8325/jukebox
 
 Library
+  default-language: Haskell2010
   Build-depends: base >= 4 && < 5, array, transformers >= 0.4.0.0, directory,
     filepath, pretty >= 1.1.2.0, symbol, dlist, process, containers, uglymemo,
-    minisat
+    minisat, utf8-string
   if !impl(ghc >= 8.0)
     Build-depends: semigroups, fail
   ghc-options: -W -fno-warn-incomplete-patterns
@@ -67,6 +68,7 @@
     Jukebox.Utils
 
 Executable jukebox
+  default-language: Haskell2010
   Main-is: executable/Main.hs
   Build-depends: base >= 4 && < 5, jukebox
   ghc-options: -W -fno-warn-incomplete-patterns
diff --git a/src/Jukebox/TPTP/Lexer.x b/src/Jukebox/TPTP/Lexer.x
--- a/src/Jukebox/TPTP/Lexer.x
+++ b/src/Jukebox/TPTP/Lexer.x
@@ -17,6 +17,7 @@
 import Data.Word
 import Data.Char
 import Data.Ratio
+import Codec.Binary.UTF8.String
 }
 
 $alpha = [a-zA-Z0-9_]
@@ -216,8 +217,8 @@
 data TokenStream = At {-# UNPACK #-} !Pos !Contents
 data Contents = Cons !Token TokenStream
 
-scan xs = go (Input (Pos 1 1) '\n' xs)
-  where go inp@(Input pos _ xs) =
+scan xs = go (Input (Pos 1 1) '\n' [] xs)
+  where go inp@(Input pos _ _ xs) =
           case alexScan inp 0 of
                 AlexEOF -> let t = At pos (Cons Eof t) in t
                 AlexError _ -> let t = At pos (Cons Error t) in t
@@ -225,20 +226,19 @@
                 AlexToken inp' len act ->
                   At pos (act (take len xs) `Cons` go inp')
 
-data AlexInput = Input {-# UNPACK #-} !Pos {-# UNPACK #-} !Char String
+data AlexInput = Input {-# UNPACK #-} !Pos {-# UNPACK #-} !Char [Word8] String
 
 alexInputPrevChar :: AlexInput -> Char
-alexInputPrevChar (Input _ c _) = c
+alexInputPrevChar (Input _ c _ _) = c
 
 {-# INLINE alexGetByte #-}
 alexGetByte :: AlexInput -> Maybe (Word8,AlexInput)
-alexGetByte i = fmap f (alexGetChar i)
-  where f (c, i') = (fromIntegral (ord c), i')
-{-# INLINE alexGetChar #-}
-alexGetChar :: AlexInput -> Maybe (Char,AlexInput)
-alexGetChar (Input p _ (x:xs)) =
-  Just (x, Input (advance p x) x xs)
-alexGetChar _ = Nothing
+alexGetByte (Input pos prev (b:bs) xs) =
+  Just (b, Input pos prev bs xs)
+alexGetByte (Input pos _ [] (x:xs)) =
+  case encodeChar x of
+    b:bs -> Just (b, Input (advance pos x) x bs xs)
+alexGetByte (Input _ _ [] []) = Nothing
 
 {-# INLINE advance #-}
 advance :: Pos -> Char -> Pos
diff --git a/src/Jukebox/Toolbox.hs b/src/Jukebox/Toolbox.hs
--- a/src/Jukebox/Toolbox.hs
+++ b/src/Jukebox/Toolbox.hs
@@ -1,5 +1,5 @@
 -- Components ("boxes") which can be put together to make tools.
-{-# LANGUAGE RecordWildCards, CPP #-}
+{-# LANGUAGE RecordWildCards, CPP, ScopedTypeVariables #-}
 module Jukebox.Toolbox where
 
 import Jukebox.Options
@@ -16,6 +16,7 @@
 import Jukebox.Tools.HornToUnit
 import System.Exit
 import System.IO
+import Control.Exception
 import Jukebox.TPTP.FindFile
 import qualified Data.Map.Strict as Map
 import qualified Jukebox.SMTLIB as SMT
@@ -97,6 +98,24 @@
   hPutStrLn stderr "You can use \"-\" to read from standard input."
   exitWith (ExitFailure 1)
 forAllFiles xs f = mapM_ f xs
+
+----------------------------------------------------------------------
+-- Read in a single file without parsing it.
+
+readTPTPFileBox :: OptionParser (FilePath -> IO String)
+readTPTPFileBox = readTPTPFile <$> findFileFlags
+
+readTPTPFile :: [FilePath] -> FilePath -> IO String
+readTPTPFile dirs path = do
+  mfile <- findFileTPTP dirs path
+  case mfile of
+    Nothing -> do
+      hPutStrLn stderr ("File '" ++ path ++ "' not found")
+      exitWith (ExitFailure 1)
+    Just file ->
+      readFile file `catch` \(e :: IOException) -> do
+        hPrint stderr e
+        exitWith (ExitFailure 1)
 
 ----------------------------------------------------------------------
 -- Read in a single problem.
diff --git a/src/Jukebox/Utils.hs b/src/Jukebox/Utils.hs
--- a/src/Jukebox/Utils.hs
+++ b/src/Jukebox/Utils.hs
@@ -31,4 +31,4 @@
   hFlush stdin
   hClose stdin
   code <- waitForProcess pid
-  fmap (code,) (hGetContents stdout) <* hClose stdout
+  fmap (code,) (hGetContents stdout)
