diff --git a/Control/Shell.hs b/Control/Shell.hs
--- a/Control/Shell.hs
+++ b/Control/Shell.hs
@@ -128,60 +128,83 @@
 --   current name.
 cpdir :: FilePath -> FilePath -> Shell ()
 cpdir fromdir todir = do
-    dir <- isDirectory todir
-    if dir
-      then go fromdir todir id
-      else go fromdir todir (joinPath . drop 1 . splitPath)
+    exists <- isDirectory todir
+    if exists
+      then do
+        mkdir True (todir </> takeFileName fromdir)
+        go fromdir (todir </> takeFileName fromdir)
+      else mkdir True todir >> go fromdir todir
   where
-    go from to dropFirstDir = do
-      forEachDirectory_ from (\dir -> mkdir True (to </> dropFirstDir dir))
+    go from to = do
+      forEachDirectory_ from (\dir -> mkdir True (to </> dir))
       forEachFile_ from $ \file -> do
-        let file' = to </> dropFirstDir file
+        let file' = to </> file
         assert (errOverwrite file') (not <$> isDirectory file')
-        cp file file'
+        cp (from </> file) file'
     errOverwrite f = "cannot overwrite directory `" ++ f
                      ++ "' with non-directory"
 
 -- | Recursively perform an action on each subdirectory of the given directory.
+--   The path passed to the callback is relative to the given directory.
 --   The action will *not* be performed on the given directory itself.
 forEachDirectory :: FilePath -> (FilePath -> Shell a) -> Shell [a]
-forEachDirectory dir f = do
-  files <- map (dir </>) <$> ls dir
-  fromdirs <- filterM isDirectory files
-  xs <- forM fromdirs $ \d -> do
-    x <- f d
-    xs <- forEachDirectory d f
-    return (x:xs)
-  return (concat xs)
+forEachDirectory root f = go ""
+  where
+    dir = if null root then "." else root
+    go subdir = do
+      let dir' = dir </> subdir
+      files <- ls dir'
+      fromdirs <- filterM (\d -> isDirectory (dir' </> d)) files
+      xs <- forM fromdirs $ \d -> do
+        let d' = subdir </> d
+        x <- f d'
+        (x:) <$> go d'
+      return (concat xs)
 
 -- | Like 'forEachDirectory', but discards its result.
 forEachDirectory_ :: FilePath -> (FilePath -> Shell ()) -> Shell ()
-forEachDirectory_ dir f = do
-  files <- map (dir </>) <$> ls dir
-  fromdirs <- filterM isDirectory files
-  forM_ fromdirs $ \d -> f d >> forEachDirectory d f
+forEachDirectory_ root f = go ""
+  where
+    dir = if null root then "." else root
+    go subdir = do
+      let dir' = dir </> subdir
+      files <- ls dir'
+      fromdirs <- filterM (\d -> isDirectory (dir' </> d)) files
+      forM_ fromdirs $ \d -> let d' = subdir </> d in f d' >> go d'
 
 -- | Perform an action on each file in the given directory.
 --   This function will traverse any subdirectories of the given as well.
 --   File paths are given relative to the given directory; the current working
 --   directory is not affected.
 forEachFile :: FilePath -> (FilePath -> Shell a) -> Shell [a]
-forEachFile dir f = do
-  files <- map (dir </>) <$> ls dir
-  xs <- filterM isFile files >>= mapM f
-  fromdirs <- filterM isDirectory files
-  xss <- forM fromdirs $ \d -> do
-    forEachFile d f
-  return $ concat (xs:xss)
+forEachFile root f = go ""
+  where
+    dir = if null root then "." else root
+    go subdir = do
+      let dir' = dir </> subdir
+      files <- ls dir'
+      
+      -- For each file in this directory...
+      onlyfiles <- filterM (\fl -> isFile (dir' </> fl)) files
+      xs <- mapM (\x -> f (subdir </> x)) onlyfiles
 
+      -- For each subdirectory...
+      fromdirs <- filterM (\fl -> isDirectory (dir' </> fl)) files
+      xss <- forM fromdirs $ \d -> do
+        go (subdir </> d)
+      return $ concat (xs:xss)
+
 -- | Like @forEachFile@ but only performs a side effect.
 forEachFile_ :: FilePath -> (FilePath -> Shell ()) -> Shell ()
-forEachFile_ dir f = do
-  files <- map (dir </>) <$> ls dir
-  filterM isFile files >>= mapM_ f
-  fromdirs <- filterM isDirectory files
-  forM_ fromdirs $ \d -> do
-    forEachFile d f
+forEachFile_ root f = go ""
+  where
+    dir = if null root then "." else root
+    go subdir = do
+      let dir' = dir </> subdir
+      files <- ls dir'
+      filterM (\fl -> isFile (dir' </> fl)) files >>= mapM_ (f . (subdir </>))
+      fromdirs <- filterM (\fl -> isDirectory (dir' </> fl)) files
+      forM_ fromdirs $ \d -> go (subdir </> d)
 
 -- | Copy a file. Fails if the source is a directory. If the target is a
 --   directory, the source file is copied into that directory using its current
diff --git a/shellmate.cabal b/shellmate.cabal
--- a/shellmate.cabal
+++ b/shellmate.cabal
@@ -1,5 +1,5 @@
 name:                shellmate
-version:             0.2.1
+version:             0.2.2
 synopsis:            Simple interface for shell scripting in Haskell.
 description:         Aims to simplify development of cross-platform shell scripts and similar things.
 homepage:            http://github.com/valderman/shellmate
