file - 过滤路径列表以仅包含文件

标签 file haskell io path

如果我有一个列表 FilePaths ,如何过滤它们以仅返回常规文件(即,不是符号链接(symbolic link)或目录)?

例如,使用 getDirectoryContents

main = do
    contents <- getDirectoryContents "/foo/bar"
    let onlyFiles = filterFunction contents in
        print onlyFiles

其中“filterFunction”是一个仅返回 FilePaths 的函数代表文件。

答案可能只适用于 Linux,但首选跨平台支持。

[编辑] 仅使用 doDirectoryExist 无法按预期工作。此脚本打印目录中所有内容的列表,而不仅仅是文件:
module Main where

import System.Directory
import Control.Monad (filterM, liftM)

getFiles :: FilePath -> IO [FilePath]
getFiles root = do
    contents <- getDirectoryContents root
    filesHere <- filterM (liftM not . doesDirectoryExist) contents
    subdirs <- filterM doesDirectoryExist contents
    return filesHere

main = do
    files <- getFiles "/"
    print $ files

此外,变量 subdirs 将只包含 "."".." .

最佳答案

要查找标准库函数,Hoogle是一个很好的资源;它是一个 Haskell 搜索引擎,可让您按类型搜索。但是,使用它需要弄清楚如何考虑 Haskell Way™ 的类型,但您建议的类型签名并不完全适用。所以:

  • 您正在寻找 [Filepath] -> [Filepath] .请记住,Haskell 的拼写是 FilePath .所以……
  • 您正在寻找 [FilePath] -> [FilePath] .这是不必要的;如果你想过滤东西,你应该使用 filter .所以……
  • 您正在寻找类型为 FilePath -> Bool 的函数您可以传递给 filter .但这并不完全正确:该函数需要查询文件系统,这是一个效果,而 Haskell 使用 IO 跟踪类型系统中的效果。 .所以……
  • 您正在寻找类型为 FilePath -> IO Bool 的函数.

  • if we search for that on Hoogle ,第一个结果是 doesFileExist :: FilePath -> IO Bool 来自 System.Directory .从文档:

    The operation doesFileExist returns True if the argument file exists and is not a directory, and False otherwise.



    所以System.Directory.doesFileExist正是你想要的。 (嗯……只需要一点额外的工作!见下文。)

    现在,你如何使用它?您不能使用 filter在这里,因为你有一个有效的功能。您可以再次使用 Hoogle – 如果 filter有类型 (a -> Bool) -> [a] -> [a] ,然后用 monad m 注释函数的结果为您提供新型 Monad m => (a -> m Bool) -> [a] -> m [Bool] – 但有一个更简单的“便宜的把戏”。一般来说,如果func是具有有效/一元版本的函数,该有效/一元版本称为 funcM ,它经常住在 Control.Monad .¹ 事实上,有一个函数 Control.Monad.filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] .

    然而!尽管我们不愿承认,即使在 Haskell 中,类型也不能提供您需要的所有信息。重要的是,我们在这里会遇到一个问题:
  • 作为函数参数给出的文件路径是相对于当前目录解释的,但是…
  • getDirectoryContents 返回相对于其参数的路径。

  • 因此,我们可以采取两种方法来解决问题。首先是调整getDirectoryContents的结果以便它们可以被正确解释。 (我们也丢弃了 ... 结果,尽管如果您只是在寻找常规文件,它们不会有任何伤害。)这将返回文件名,其中包括正在检查其内容的目录。调整getDirectoryContents函数看起来像这样:
    getQualifiedDirectoryContents :: FilePath -> IO [FilePath]
    getQualifiedDirectoryContents fp =
        map (fp </>) . filter (`notElem` [".",".."]) <$> getDirectoryContents fp
    
    filter摆脱特殊目录和 map将参数目录添加到所有结果中。这使得返回的文件可以接受 doesFileExist 的参数。 . (如果您以前没有见过它们, (System.FilePath.</>) 会附加两个文件路径;而 (Control.Applicative.<$>) 也可用作 (Data.Functor.<$>) ,是 fmap 的中缀同义词,类似于 liftM 但适用范围更广。 )

    将所有这些放在一起,您的最终代码变为:
    import Control.Applicative
    import Control.Monad
    import System.FilePath
    import System.Directory
    
    getQualifiedDirectoryContents :: FilePath -> IO [FilePath]
    getQualifiedDirectoryContents fp =
        map (fp </>) . filter (`notElem` [".",".."]) <$> getDirectoryContents fp
    
    main :: IO ()
    main = do
      contents  <- getQualifiedDirectoryContents "/foo/bar"
      onlyFiles <- filterM doesFileExist contents
      print onlyFiles
    

    或者,如果你喜欢花哨/无点:
    import Control.Applicative
    import Control.Monad
    import System.FilePath
    import System.Directory
    
    getQualifiedDirectoryContents :: FilePath -> IO [FilePath]
    getQualifiedDirectoryContents fp =
        map (fp </>) . filter (`notElem` [".",".."]) <$> getDirectoryContents fp
    
    main :: IO ()
    main =   print
         =<< filterM doesFileExist
         =<< getQualifiedDirectoryContents "/foo/bar"
    

    第二种方法是调整东西,使doesFileExist使用适当的当前目录运行。这将仅返回与正在检查其内容的目录相关的文件名。为此,我们要使用 withCurrentDirectory :: FilePath -> IO a -> IO a 函数(但见下文),然后通过 getDirectoryContents当前目录 "."争论。 withCurrentDirectory 的文档说(部分):

    Run an IO action with the given working directory and restore the original working directory afterwards, even if the given action fails due to an exception.



    将所有这些放在一起为我们提供了以下代码
    import Control.Monad
    import System.Directory
    
    main :: IO ()
    main = withCurrentDirectory "/foo/bar" $
             print =<< filterM doesFileExist =<< getDirectoryContents "."
    

    这正是我们想要的,但不幸的是,它仅在 directory 的 1.3.2.0 版本中可用。包 - 在撰写本文时,是最新的,而不是我拥有的。幸运的是,这是一个很容易实现的功能;这样的 set-a-value-locally 函数通常是根据 Control.Exception.bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c 实现的. bracket函数作为 bracket before after action 运行,并正确处理异常。所以我们可以定义 withCurrentDirectory我们自己:
    withCurrentDirectory :: FilePath -> IO a -> IO a
    withCurrentDirectory fp m =
      bracket getCurrentDirectory setCurrentDirectory $ \_ -> do
        setCurrentDirectory fp
        m
    

    然后使用它来获得最终代码:
    import Control.Exception
    import Control.Monad
    import System.Directory
    
    withCurrentDirectory :: FilePath -> IO a -> IO a
    withCurrentDirectory fp m =
      bracket getCurrentDirectory setCurrentDirectory $ \_ -> do
        setCurrentDirectory fp
        m
    
    main :: IO ()
    main = withCurrentDirectory "/foo/bar" $
             print =<< filterM doesFileExist =<< getDirectoryContents "."
    

    另外,关于 let 的一个快速说明s 在 do s:在 do堵塞,
    do ...foo...
       let x = ...bar...
       ...baz...
    

    相当于
    do ...foo...
       let x = ...bar... in
         do ...baz...
    

    所以你的示例代码不需要 inlet并且可以缩小 print称呼。

    ¹ 并非总是如此:有时您需要不同类别的效果!使用 Applicative 来自 Control.Applicative 如果可能;更多的东西是Applicative比是 Monad s(尽管这意味着您可以减少使用它们)。在那种情况下,有效的函数可能存在于那里,或者也存在于 Data.Foldable 中。或 Data.Traversable .

    关于file - 过滤路径列表以仅包含文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31419429/

    相关文章:

    Haskell 2 小数点

    c - 回复 : Reading a line and using realloc in C

    javascript - 在 IE9 中将用户指定的文件作为字符串读取

    java - 列出文件夹和子文件夹的文件路径

    java - 从文件中获取值并将它们分配给java中的数组

    haskell - 尝试一些 Yesod 示例时出现编译错误

    string - Haskell 从文件中删除尾随和前导空格

    c# - IO操作的并发问题

    python - 如何在 Haskell 中表达这个 Python for 循环?

    java - 遍历目录查找特定文件夹