gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 01:34:49 28 4
gpt4 key购买 nike

如果我有一个列表 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/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com