【问题标题】:Recursively search directories for all files matching name criteria in Haskell在 Haskell 中递归搜索与名称条件匹配的所有文件的目录
【发布时间】:2018-08-06 16:33:55
【问题描述】:

我在 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 相当满意,但如果我们开始对 monad 和应用函子过于繁重,你需要放慢速度(我使用的 haskell 不足以让我记住这一点)。不过,我发现关于 hackage 的 haskell 文档难以理解。

【问题讨论】:

  • GlobPattern 的文档没有提到支持?;也许这就是问题的一部分。
  • @DanielWagner 谢谢 好的,我会试试这个。我的答案看起来也不错,我明天也试试。

标签: haskell filepath file-extension


【解决方案1】:

所以,我们差不多有 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 -&gt; Bool函数来过滤不需要的目录;还有一个初始状态b 和一个转换函数b -&gt; FilePath -&gt; IO b 处理文件名,更新b 状态并可能有一些副作用。请注意,状态的类型是由调用者选择的,调用者可能会在其中放置有用的东西。

如果我们只想在生成时打印文件名,我们可以这样做:

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

我们使用() 作为虚拟状态,因为我们在这里并不真正需要它。

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

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

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

【讨论】:

    【解决方案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)。

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

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

    Here 是您想尝试的代码。

    【讨论】:

      猜你喜欢
      • 2014-10-04
      • 2012-04-28
      • 2018-08-26
      • 2012-04-07
      • 1970-01-01
      • 1970-01-01
      • 2017-05-17
      • 2021-05-25
      相关资源
      最近更新 更多