diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,7 @@
+# 1.8.1
+
+There is a new function `cp_should_follow_symlinks` to specify whether a copy should follow symlinks.
+
 # 1.8.0
 
 * `cp_r` now uses upper case R: `cp -R`
diff --git a/shelly.cabal b/shelly.cabal
--- a/shelly.cabal
+++ b/shelly.cabal
@@ -1,6 +1,6 @@
 Name:       shelly
 
-Version:     1.8.0
+Version:     1.8.1
 Synopsis:    shell-like (systems) programming in Haskell
 
 Description: Shelly provides convenient systems programming in Haskell,
@@ -44,16 +44,17 @@
 
 Library
   Exposed-modules: Shelly, Shelly.Lifted, Shelly.Pipe, Shelly.Unix
-  other-modules:   Shelly.Base, Shelly.Find
+  other-modules:   Shelly.Base, Shelly.Find, Shelly.Directory
   hs-source-dirs: src
 
   Build-depends:
     containers                >= 0.4.2.0,
     time                      >= 1.3 && < 1.9,
-    directory                 >= 1.1.0.0 && < 1.4.0.0,
+    directory                 >= 1.3.0.0 && < 1.4.0.0,
     mtl                       >= 2,
     process                   >= 1.0,
     unix-compat               < 0.6,
+    unix,
     system-filepath           >= 0.4.7 && < 0.5,
     system-fileio             < 0.4,
     monad-control             >= 0.3.2 && < 1.1,
@@ -125,9 +126,10 @@
     async,
     bytestring                >= 0.10,
     containers                >= 0.5.0.0,
-    directory                 >= 1.1.0.0 && < 1.4.0.0,
+    directory                 >= 1.3.0.0 && < 1.4.0.0,
     process                   >= 1.1.0,
     unix-compat               < 0.6,
+    unix,
     system-filepath           >= 0.4.7 && < 0.5,
     system-fileio             < 0.4,
     time                      >= 1.3 && < 1.9,
diff --git a/src/Shelly.hs b/src/Shelly.hs
--- a/src/Shelly.hs
+++ b/src/Shelly.hs
@@ -94,6 +94,7 @@
          ) where
 
 import Shelly.Base
+import Shelly.Directory
 import Shelly.Find
 import Control.Monad ( when, unless, void, forM, filterM, liftM2 )
 import Control.Monad.Trans ( MonadIO )
@@ -141,7 +142,7 @@
 import Filesystem hiding (canonicalizePath)
 import qualified Filesystem.Path.CurrentOS as FP
 
-import System.Directory ( setPermissions, getPermissions, Permissions(..), getTemporaryDirectory )
+import System.Directory ( setPermissions, getPermissions, Permissions(..), getTemporaryDirectory, pathIsSymbolicLink )
 import Data.Char (isDigit)
 
 import Data.Tree(Tree(..))
@@ -1398,8 +1399,8 @@
 cp_r from' to' = do
     from <- absPath from'
     fromIsDir <- (test_d from)
-    if not fromIsDir then cp from' to' else do
-       trace $ "cp -R " <> toTextIgnore from <> " " <> toTextIgnore to'
+    if not fromIsDir then cp_should_follow_symlinks False from' to' else do
+       trace $ "cp_r " <> toTextIgnore from <> " " <> toTextIgnore to'
        to <- absPath to'
        toIsDir <- test_d to
 
@@ -1415,19 +1416,25 @@
 -- | Copy a file. The second path could be a directory, in which case the
 -- original file name is used, in that directory.
 cp :: FilePath -> FilePath -> Sh ()
-cp from' to' = do
+cp = cp_should_follow_symlinks True
+
+cp_should_follow_symlinks :: Bool -> FilePath -> FilePath -> Sh ()
+cp_should_follow_symlinks shouldFollowSymlinks from' to' = do
   from <- absPath from'
   to <- absPath to'
   trace $ "cp " <> toTextIgnore from <> " " <> toTextIgnore to
   to_dir <- test_d to
   let to_loc = if to_dir then to FP.</> filename from else to
-  liftIO $ copyFile from to_loc `catchany` (\e -> throwIO $
-      ReThrownException e (extraMsg to_loc from)
-    )
+  if shouldFollowSymlinks then copyNormal from to_loc else do
+    isSymlink <- liftIO $ pathIsSymbolicLink (encodeString from)
+    if not isSymlink then copyNormal from to_loc else do
+      target <- liftIO $ getSymbolicLinkTarget (encodeString from)
+      liftIO $ createFileLink target (encodeString to_loc)
   where
     extraMsg t f = "during copy from: " ++ encodeString f ++ " to: " ++ encodeString t
-
-
+    copyNormal from to = liftIO $ copyFile from to `catchany` (\e -> throwIO $
+          ReThrownException e (extraMsg to from)
+        )
 
 -- | Create a temporary directory and pass it as a parameter to a Sh
 -- computation. The directory is nuked afterwards.
diff --git a/src/Shelly/Directory.hs b/src/Shelly/Directory.hs
new file mode 100644
--- /dev/null
+++ b/src/Shelly/Directory.hs
@@ -0,0 +1,36 @@
+{-# OPTIONS -Wall #-}
+module Shelly.Directory where
+
+import System.IO.Error (modifyIOError, ioeSetLocation, ioeGetLocation)
+import qualified Filesystem.Path.CurrentOS as FP
+
+#ifdef mingw32_HOST_OS
+import qualified System.Win32 as Win32
+#else
+import qualified System.Posix as Posix
+#endif
+
+createFileLink :: String -> String -> IO ()
+createFileLink target link =
+  (`ioeAddLocation` "createFileLink") `modifyIOError` do
+#ifdef mingw32_HOST_OS
+    Win32.createSymbolicLink False target link
+#else
+    Posix.createSymbolicLink target link
+#endif
+
+getSymbolicLinkTarget :: String -> IO String
+getSymbolicLinkTarget path =
+  (`ioeAddLocation` "getSymbolicLinkTarget") `modifyIOError` do
+#ifdef mingw32_HOST_OS
+    Win32.readSymbolicLink path
+#else
+    Posix.readSymbolicLink path
+#endif
+
+ioeAddLocation :: IOError -> String -> IOError
+ioeAddLocation e loc = do
+  ioeSetLocation e newLoc
+  where
+    newLoc = loc ++ if Prelude.null oldLoc then "" else ":" ++ oldLoc
+    oldLoc = ioeGetLocation e
