diff --git a/spy.cabal b/spy.cabal
--- a/spy.cabal
+++ b/spy.cabal
@@ -1,5 +1,5 @@
 name:               spy
-version:            0.9
+version:            0.10
 license:            BSD3
 license-file:       LICENSE
 author:             Stefan Saasen
@@ -43,7 +43,7 @@
 
 executable  spy
   main-is:          Main.hs
-  other-modules:    Spy.Watcher
+  other-modules:    Spy.Watcher, Spy.Run
   build-depends:
                     base < 5 && >= 3,
                     fsnotify >= 0.0.4,
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -7,7 +7,7 @@
 import Spy.Watcher
 
 version :: String
-version = "spy v0.9, (C) Stefan Saasen"
+version = "spy v0.10, (C) Stefan Saasen"
 
 watch :: Spy
 watch = Watch
diff --git a/src/Spy/Run.hs b/src/Spy/Run.hs
new file mode 100644
--- /dev/null
+++ b/src/Spy/Run.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE DoAndIfThenElse #-}
+
+module Spy.Run (
+    runIndefinitely
+) where
+
+
+import           System.IO          (isEOF)
+import           System.Exit
+import           System.Posix.Signals
+import           Control.Concurrent
+import qualified Control.Exception as E
+
+-- | Run indefinitely until the users aborts (via CTRL-D, CTRL-C or by sending a TERM
+-- signal)
+-- The `start` funtion will be called once, the cleanup once when the user
+-- aborts with the result of the start function as the argument.
+runIndefinitely :: IO b -> (b -> IO a) -> IO ()
+runIndefinitely start cleanup = do
+    tid <- myThreadId
+    res <- start
+    let handler = CatchOnce . terminate res tid
+    installHandler sigINT (handler 130) Nothing -- CTRL-C
+    installHandler sigTERM (handler 143) Nothing
+    loop res
+    where loop res = do
+            finished <- isEOF
+            if finished then do
+                cleanup res
+                return ()
+            else do
+                _ <- getLine
+                loop res
+          terminate res tid exit = do
+            cleanup res
+            E.throwTo tid $ ExitFailure exit
+
