Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the haskell way to copy a directory

Tags:

haskell

I find myself doing more and more scripting in haskell. But there are some cases where I'm really not sure of how to do it "right".
e.g. copy a directory recursively (a la unix cp -r).

Since I mostly use linux and Mac Os I usually cheat:

import System.Cmd
import System.Exit

copyDir ::  FilePath -> FilePath -> IO ExitCode
copyDir src dest = system $ "cp -r " ++ src ++ " " ++ dest

But what is the recommended way to copy a directory in a platform independent fashion?
I didn't find anything suitable on hackage.

This is my rather naiv implementation I use so far:

import System.Directory
import System.FilePath((</>))
import Control.Applicative((<$>))
import Control.Exception(throw)
import Control.Monad(when,forM_)

copyDir ::  FilePath -> FilePath -> IO ()
copyDir src dst = do
  whenM (not <$> doesDirectoryExist src) $
    throw (userError "source does not exist")
  whenM (doesFileOrDirectoryExist dst) $
    throw (userError "destination already exists")

  createDirectory dst
  content <- getDirectoryContents src
  let xs = filter (`notElem` [".", ".."]) content
  forM_ xs $ \name -> do
    let srcPath = src </> name
    let dstPath = dst </> name
    isDirectory <- doesDirectoryExist srcPath
    if isDirectory
      then copyDir srcPath dstPath
      else copyFile srcPath dstPath

  where
    doesFileOrDirectoryExist x = orM [doesDirectoryExist x, doesFileExist x]
    orM xs = or <$> sequence xs
    whenM s r = s >>= flip when r

Any suggestions of what really is the way to do it?


I updated this with the suggestions of hammar and FUZxxl.
...but still it feels kind of clumsy to me for such a common task!

like image 953
oliver Avatar asked Jul 24 '11 13:07

oliver


4 Answers

It's possible to use the Shelly library in order to do this, see cp_r:

cp_r "sourcedir" "targetdir"

Shelly first tries to use native cp -r if available. If not, it falls back to a native Haskell IO implementation.

For further details on type semantics of cp_r, see this post written by me to described how to use cp_r with String and or Text.

Shelly is not platform independent, since it relies on the Unix package, which is not supported under Windows.

like image 156
Uli Köhler Avatar answered Sep 30 '22 03:09

Uli Köhler


I couldn't find anything that does this on Hackage.

Your code looks pretty good to me. Some comments:

  1. dstExists <- doesDirectoryExist dst
    

    This does not take into account that a file with the destination name might exist.

  2. if or [not srcExists, dstExists] then print "cannot copy"
    

    You might want to throw an exception or return a status instead of printing directly from this function.

  3. paths <- forM xs $ \name -> do
        [...]
      return ()
    

    Since you're not using paths for anything, you can change this to

    forM_ xs $ \name -> do
      [...]
    
like image 39
hammar Avatar answered Sep 30 '22 05:09

hammar


The filesystem-trees package provides the means for a very simple implementation:

import System.File.Tree (getDirectory, copyTo_)

copyDirectory :: FilePath -> FilePath -> IO ()
copyDirectory source target = getDirectory source >>= copyTo_ target
like image 32
Johannes Gerer Avatar answered Sep 30 '22 05:09

Johannes Gerer


The MissingH package provides recursive directory traversals, which you might be able to use to simplify your code.

like image 23
kowey Avatar answered Sep 30 '22 04:09

kowey