Lan*_*ton 11 io haskell file path
如果我有一个列表FilePaths,如何过滤它们只返回常规文件(即不是符号链接或目录)?
例如,使用 getDirectoryContents
main = do
contents <- getDirectoryContents "/foo/bar"
let onlyFiles = filterFunction contents in
print onlyFiles
Run Code Online (Sandbox Code Playgroud)
其中"filterFunction"是一个只返回FilePaths表示文件的函数.
答案可能仅适用于Linux,但首选跨平台支持.
[编辑]只是使用didDirectoryExist不能按预期工作.此脚本打印目录中所有内容的列表,而不仅仅是文件:
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
Run Code Online (Sandbox Code Playgroud)
此外,变量子目录只包含"."和"..".
Ant*_*sky 19
为了找到标准库函数,Hoogle是一个很好的资源; 它是一个Haskell搜索引擎,可让您按类型进行搜索.使用它需要弄清楚如何考虑Haskell Way™的类型,但是你提出的类型签名并不适用.所以:
你在找[Filepath] -> [Filepath].记住,Haskell拼写是FilePath.所以…
你在找[FilePath] -> [FilePath].这是不必要的; 如果你想过滤东西,你应该使用filter.所以…
您正在寻找FilePath -> Bool可以传递给的类型函数filter.但这可能不太正确:这个函数需要查询文件系统,这是一个效果,而Haskell跟踪类型系统中的效果使用IO.所以…
你正在寻找一种类型的功能FilePath -> IO Bool.
而且,如果我们搜索上Hoogle,第一个结果是doesFileExist :: FilePath -> IO Bool从System.Directory.来自文档:
如果参数文件存在且不是目录,则操作
doesFileExist返回True,False否则返回.
所以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以便正确解释它们.(我们也会丢弃.和..结果,但如果你只是寻找常规文件,它们不会伤害任何东西.)这将返回包含正在检查其内容的目录的文件名.adjust getDirectoryContents函数如下所示:
getQualifiedDirectoryContents :: FilePath -> IO [FilePath]
getQualifiedDirectoryContents fp =
map (fp </>) . filter (`notElem` [".",".."]) <$> getDirectoryContents fp
Run Code Online (Sandbox Code Playgroud)
在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
Run Code Online (Sandbox Code Playgroud)
或者,如果您想要花哨/无点:
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"
Run Code Online (Sandbox Code Playgroud)
第二种方法是调整事物,以便doesFileExist使用适当的当前目录运行.这将仅返回相对于正在检查其内容的目录的文件名.为此,我们希望使用该withCurrentDirectory :: FilePath -> IO a -> IO a函数(但见下文),然后传递getDirectoryContents当前目录"."参数.文件withCurrentDirectory说(部分):
IO使用给定的工作目录运行操作,然后恢复原始工作目录,即使给定的操作因异常而失败.
将所有这些放在一起为我们提供了以下代码
import Control.Monad
import System.Directory
main :: IO ()
main = withCurrentDirectory "/foo/bar" $
print =<< filterM doesFileExist =<< getDirectoryContents "."
Run Code Online (Sandbox Code Playgroud)
这就是我们想要的,但不幸的是,它只能在directory包的1.3.2.0版本中使用- 在撰写本文时,是最新版本,而不是我拥有的版本.幸运的是,这是一个很容易实现的功能; 这样的set-a-value-local函数通常用来实现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
Run Code Online (Sandbox Code Playgroud)
然后使用它来获取最终代码:
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 "."
Run Code Online (Sandbox Code Playgroud)
另外,关于lets中dos的快速说明:在一个do块中,
do ...foo...
let x = ...bar...
...baz...
Run Code Online (Sandbox Code Playgroud)
相当于
do ...foo...
let x = ...bar... in
do ...baz...
Run Code Online (Sandbox Code Playgroud)
因此,您的示例代码不需要inin let并且可以使print调用更加突出.
¹并非总是如此:有时您需要不同类别的效果!使用Applicative从Control.Applicative可能的情况下,Applicative比Monads 更多的东西(虽然这意味着你可以少用它们).在这种情况下,有效的功能可能存在于那里,或者也可能存在于Data.Foldable或Data.Traversable.
对于Unix系统,该软件包unix公开了这些API:
您可以使用它们的组合来实现您想要的效果.在GHCI中使用它们的示例演示:
?> import System.Posix.Files
?> status <- getFileStatus "/home/sibi"
?> isDirectory status
True
?> isRegularFile status
False
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1684 次 |
| 最近记录: |