Что такое метод haskell для копирования каталога

Я считаю, что все больше и больше скриптов в haskell. Но есть некоторые случаи, когда я действительно не уверен, как это сделать "правильно".
например скопируйте каталог рекурсивно (a la unix cp -r).

Поскольку я в основном использую Linux и Mac Os, я обычно обманываю:

import System.Cmd
import System.Exit

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

Но каков рекомендуемый способ скопировать каталог в независимую от платформы?
Я не нашел ничего подходящего для взлома.

Это моя довольно наивная реализация, которую я использую до сих пор:

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

Любые предложения о том, что на самом деле способ сделать это?


Я обновил это с предложениями hammar и FUZxxl.
... но все же для меня такая неуклюжая для такой общей задачи!

Ответы

Ответ 1

Я не мог найти ничего, что делает это в Hackage.

Ваш код выглядит очень хорошо для меня. Некоторые комментарии:

  • dstExists <- doesDirectoryExist dst
    

    Это не учитывает, что файл с именем назначения может существовать.

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

    Возможно, вы захотите выбросить исключение или вернуть статус вместо печати непосредственно из этой функции.

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

    Поскольку вы не используете paths для чего-либо, вы можете изменить это на

    forM_ xs $ \name -> do
      [...]
    

Ответ 2

Для этого можно использовать библиотеку Shelly, см. cp_r:

cp_r "sourcedir" "targetdir"

Сначала Шелли пытается использовать нативный cp -r если он доступен. Если нет, то он возвращается к собственной реализации IO Haskell.

Для получения дополнительной информации о семантике типов cp_r, см. Этот пост, написанный мной для описания того, как использовать cp_r со String и или Text.

Shelly не зависит от платформы, поскольку полагается на пакет Unix, который не поддерживается в Windows.

Ответ 3

Пакет MissingH обеспечивает рекурсивный обход каталога, который вы можете использовать для упрощения кода.

Ответ 4

Пакет filesystem-trees предоставляет средства для очень простой реализации:

import System.File.Tree (getDirectory, copyTo_)

copyDirectory :: FilePath -> FilePath -> IO ()
copyDirectory source target = getDirectory source >>= copyTo_ target

Ответ 5

Я предполагаю, что функция в Path.IO copyDirRecur с вариантами включения/исключения символических ссылок может быть более новым и поддерживаемым решением. Требуется преобразовать Path x Dir к файлу в Path x Dir что достигается с помощью parseRelDir соответствующего parseAbsDir, но я думаю, чтобы иметь более точный тип даты, чем FilePath стоит избегать сложных ошибок во время выполнения.

Ответ 6

Есть также некоторые функции для копирования файлов и каталогов в базовые модули Cabal библиотеки Haskell, в частности Distribution.Simple.Utils в пакете Cabal. copyDirectoryRecursive - один, и есть другие функции около этого в этом модуле.