我在 Haskell 中有一个目录遍历函数,但我希望它忽略符号链接(symbolic link)。我想出了如何单独过滤文件,尽管使用了一个稍微不雅的辅助 filterM
。但经过一番诊断后,我意识到我无法过滤符号链接(symbolic link)目录。
我希望能够写出这样的东西:
-- Lazily return (normal) files from rootdir
getAllFiles :: FilePath -> IO [FilePath]
getAllFiles root = do
nodes <- pathWalkLazy root
-- get file paths from each node
let files = [dir </> file | (dir, _, files) <- nodes,
file <- files,
not . pathIsSymbolicLink dir]
normalFiles <- filterM (liftM not . pathIsSymbolicLink) files
return normalFiles
但是,我尝试过的所有变体都会得到某种版本的“无法将预期类型‘Bool’与实际类型‘IO Bool’匹配”消息(在理解中没有过滤子句,它可以工作,但无法过滤那些链接的目录)。
关于我可能完全重组该函数的方法的各种提示都以在线资源的部分形式提供,但我很确定每个此类变体都会遇到一些类似的问题。列表理解肯定是最直接的方法......如果我能以某种方式排除那些链接的目录。
后续:不幸的是,ChrisB 善意提供的解决方案的行为(几乎?!)与我现有的版本相同。我定义了三个函数,并在测试程序中运行它们:
-- XXX: debugging
files <- getAllFilesRaw rootdir
putStrLn ("getAllFilesRaw: " ++ show (length files))
files' <- getAllFilesNoSymFiles rootdir
putStrLn ("getAllFilesNoSymFiles: " ++ show (length files'))
files'' <- getAllFilesNoSymDirs rootdir
putStrLn ("getAllFilesNoSymDirs: " ++ show (length files''))
第一个是我删除了 normalFiles
过滤器的版本。第二个是我的原始版本(减去列表中的类型错误)。最后一项是 ChrisB 的建议。
运行它,然后运行系统find
实用程序:
% find $CONDA_PREFIX -type f | wc -l
449667
% find -L $CONDA_PREFIX -type f | wc -l
501153
% haskell/find-dups $CONDA_PREFIX
getAllFilesRaw : 501153
getAllFilesNoSymFiles: 464553
getAllFilesNoSymDirs: 464420
此外,出现这个问题是因为——为了我自己的自学——我已经用多种语言实现了相同的应用程序:Python;戈兰;锈; Julia ; typescript ; Bash,除了故障,Haskell;其他的正在计划中。这些程序实际上对文件做了更多的事情,但这不是这个问题的重点。
重点是所有其他语言都报告与系统find
工具相同的数字。而且,具体问题是这样的:
% ls -l /home/dmertz/miniconda3/pkgs/ncurses-6.2-he6710b0_1/lib/terminfo
lrwxrwxrwx 1 dmertz dmertz 17 Apr 29 2020 /home/dmertz/miniconda3/pkgs/ncurses-6.2-he6710b0_1/lib/terminfo -> ../share/terminfo
这里大约有 16k 个示例(当前在我的系统上),但是查看该工具其他版本中的一些示例,我特别发现所有其他语言都排除了该符号链接(symbolic link)目录的内容。
最佳答案
编辑:
- 我们现在想要处理 find 的行为,而不是仅仅修复 Bool/IO Bool 问题。
- 查看文档后, 这似乎很难合理地实现 与 PathWalk 库一起使用,所以我只是手动滚动它。 (按照评论中的要求,使用 do 符号。) 在我的快速而肮脏的测试中,结果与 find 的结果相匹配:
import System.FilePath
import System.Directory
getAllFiles' :: FilePath -> IO [FilePath]
getAllFiles' path = do
isSymlink <- pathIsSymbolicLink path
if isSymlink
-- if this is a symlink, return the empty list.
-- even if this was the original root. (matches find's behavior)
then return []
else do
isFile <- doesFileExist path
if isFile
then return [path] -- if this is a file, return it
else do
-- if it's not a file, we assume it to be a directory
dirContents <- listDirectory path
-- run this function recursively on all the children
-- and accumulate the results
fmap concat $ mapM (getAllFiles' . (path </>)) dirContents
解决 IO Bool/Bool 问题的原始答案
getAllFiles :: FilePath -> IO [FilePath]
getAllFiles root = pathWalkLazy root
-- remove dirs that are symlinks
>>= filterM (\(dir, _, _) -> fmap not $ pathIsSymbolicLink dir)
-- flatten to list of files
>>= return . concat . map (\(dir, _, files) -> map (\f -> dir </> f) files)
-- remove files that are symlinks
>>= filterM (fmap not . pathIsSymbolicLink)
关于haskell - getAllFiles(但不是符号链接(symbolic link)),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68869527/