在Haskell中递归搜索目录以查找与名称条件匹配的所有文件

时间:2018-08-06 16:33:56

标签: haskell filepath file-extension

我对Haskell缺乏经验,我想提高自己的水平,因此对于我的学习项目,我有以下要求:

  • 我想从指定的顶层目录开始搜索,不一定是绝对路径。
  • 我想查找给定扩展名的所有文件,例如.md
  • 我不想搜索隐藏的目录,例如toplevel/.excluded
  • 我希望能够忽略gedit生成.filename.md.swp之类的隐藏文件。
  • 由于我的功能,我希望得到完整的文件列表。

我搜索了整个SO。这是我到目前为止的内容:

import qualified System.FilePath.Find as SFF
import qualified Filesystem.Path.CurrentOS as FP

srcFolderName = "src"
outFolderName = "output"
resFolderName = "res"

ffNotHidden :: SFF.FindClause Bool
ffNotHidden = SFF.fileName SFF./~? ".?*"

ffIsMD :: SFF.FindClause Bool
ffIsMD = SFF.extension SFF.==? ".md" SFF.&&? SFF.fileName SFF./~? ".?*"

findMarkdownSources :: FilePath -> IO [FilePath]
findMarkdownSources filePath = do
    paths <- SFF.find ffNotHidden ffIsMD filePath
    return paths

这不起作用。在“ findMarkdownSources”中进行printf样式的调试后,我可以验证filePath是否正确,例如"/home/user/testdata"(打印出来的文件中包含“,以防万一。)列表paths始终为空。我绝对确定我指定的目录中有markdown文件(查找/ path / to / dir -name“ * .md”找到它们)。

因此,我有一些具体问题。

  1. 例如,是否有原因(过滤器不正确),为什么该代码不起作用?
  2. haskell中有多种方法可以执行此操作。似乎至少有六个软件包(fileman,system.directory,system.filepath.find)专用于此。这是回答以下问题的一些问题:

    1. Streaming recursive descent of a directory in Haskell
    2. Is there some directory walker in Haskell?
    3. avoid recursion into specifc folder using filemanip

    每个人都有三种独特的方式来实现我想要的目标,因此,我们几乎有10种方式来实现目标...

  3. 有没有一种特定的方式我应该这样做?如果可以,为什么?如果有帮助,一旦有了文件列表,我将遍历整个过程,打开并解析每个文件。

如果有帮助,我对基本的haskell会比较满意,但是如果我们开始对单子和应用函子开始过于沉重,您就需要放慢速度(我没有足够地使用haskell来保持头脑清醒)。不过,我发现有关Hackage的Haskell文档令人难以理解。

2 个答案:

答案 0 :(得分:5)

  

所以,我们差不多有10种方法可以做到这一点...

这是使用directoryfilepathextra软件包中的函数的另一种方法,但不要使用太多monad向导:

import Control.Monad (foldM)
import System.Directory (doesDirectoryExist, listDirectory) -- from "directory"
import System.FilePath ((</>), FilePath) -- from "filepath"
import Control.Monad.Extra (partitionM) -- from the "extra" package

traverseDir :: (FilePath -> Bool) -> (b -> FilePath -> IO b) -> b -> FilePath -> IO b
traverseDir validDir transition =
    let go state dirPath =
            do names <- listDirectory dirPath
               let paths = map (dirPath </>) names
               (dirPaths, filePaths) <- partitionM doesDirectoryExist paths
               state' <- foldM transition state filePaths -- process current dir
               foldM go state' (filter validDir dirPaths) -- process subdirs
     in go

这个想法是用户通过FilePath -> Bool函数来过滤不需要的目录。还有一个初始状态b和一个转换函数b -> FilePath -> IO b,用于处理文件名,更新b状态,并且可能会有一些副作用。请注意,状态的类型由调用者选择,调用者可以在其中放置有用的内容。

如果我们只想打印文件名,则可以执行以下操作:

traverseDir (\_ -> True) (\() path -> print path) () "/tmp/somedir"

我们将()用作虚拟状态,因为我们在这里实际上并不需要它。

如果我们想将文件累积到一个列表中,可以这样做:

traverseDir (\_ -> True) (\fs f -> pure (f : fs)) [] "/tmp/somedir" 

如果我们要过滤一些文件怎么办?我们需要调整传递给traverseDir的转换函数,以使其忽略它们。

答案 1 :(得分:2)

我在您的计算机上测试了您的代码,它似乎可以正常工作。这是一些示例数据:

$ find test/data
test/data
test/data/look-a-md-file.md
test/data/another-dir
test/data/another-dir/shown.md
test/data/.not-shown.md
test/data/also-not-shown.md.bkp
test/data/.hidden
test/data/some-dir
test/data/some-dir/shown.md
test/data/some-dir/.ahother-hidden
test/data/some-dir/.ahother-hidden/im-hidden.md

运行功能会导致:

ghci> findMarkdownSources "test"
["test/data/another-dir/shown.md","test/data/look-a-md-file.md","test/data/some-dir/shown.md"]

我已经用绝对路径测试了它,它也可以工作。您确定您通过了有效路径吗?在这种情况下,您会得到一个空列表(尽管您也会收到警告)。

请注意,您的代码可以简化如下:

module Traversals.FileManip where

import           Data.List            (isPrefixOf)
import           System.FilePath.Find (always, extension, fileName, find, (&&?),
                                       (/~?), (==?))

findMdSources :: FilePath -> IO [FilePath]
findMdSources fp = find isVisible (isMdFile &&? isVisible) fp
    where
      isMdFile = extension ==? ".md"
      isVisible = fileName /~? ".?*"

您甚至可以删除fp参数,但是为了清楚起见,我将其保留在此处。

我更喜欢显式导入,以便知道每个函数的来源(因为我不知道任何具有高级符号导航的Haskell IDE)。

但是,请注意,该解决方案使用了is not recommended的不安全交错IO。

因此,关于您的问题2和3,我建议使用流式解决方案,例如pipes或管道。坚持使用此类解决方案将减少您的选择(就像坚持使用纯函数式编程语言会减少我对编程语言的选择一样;)。 Here上有一个示例,说明了如何使用管道来遍历目录。

Here是您想尝试的代码。