Рекурсивный список всех файлов в каталоге с использованием Pipes

Я закончил читать руководство по каналам и Я хотел написать функцию для рекурсивного перечисления всех файлов в каталоге. Я пробовал со следующим кодом:

enumFiles :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
enumFiles path =
  PS.bracket (openDirStream path) (closeDirStream) loop
  where
    loop :: DirStream -> Producer' FilePath (PS.SafeT IO) ()
    loop ds = PS.liftBase (readDirStream ds) >>= checkName
      where
        checkName :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
        checkName ""   = return ()
        checkName "."  = loop ds
        checkName ".." = loop ds
        checkName name = PS.liftBase (getSymbolicLinkStatus newPath)
                         >>= checkStat newPath
          where newPath = path </> name

        checkStat path stat
          | isRegularFile stat = yield path >> loop ds
          | isDirectory stat = enumFiles path
          | otherwise = loop ds

Однако этот производитель завершится, как только будет достигнуто return (). Я предполагаю, что я не сочиняю это правильно, но я не вижу, как правильно это сделать.


person Damian Nadales    schedule 30.05.2017    source источник


Ответы (1)


Просто измените эту строку:

| isDirectory stat = enumFiles path

to

| isDirectory stat = enumFiles path >> loop ds

В этом рекурсивном случае в коде отсутствовала рекурсия.

Вы также можете разбить этого производителя на состав более мелких производителей и трубок:

{-# LANGUAGE RankNTypes #-}

module Main where

import qualified Pipes.Prelude as P
import qualified Pipes.Safe as PS

import           Control.Monad
import           Pipes
import           System.FilePath.Posix
import           System.Posix.Directory
import           System.Posix.Files

readDirStream' :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
readDirStream' dirpath =
  PS.bracket (openDirStream dirpath) closeDirStream (forever . loop)
  where
    loop stream =
      liftIO (readDirStream stream) >>= yield

enumFiles :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
enumFiles path =
  readDirStream' path
    >-> P.takeWhile (/= "")
    >-> P.filter (not . flip elem [".", ".."])
    >-> P.map (path </>)
    >-> forever (do
                    entry <- await
                    status <- liftIO $ getSymbolicLinkStatus entry
                    when (isDirectory status) (enumFiles entry)
                    when (isRegularFile status) (yield entry))

main :: IO ()
main =
  PS.runSafeT $ runEffect (enumFiles "/tmp" >-> P.stdoutLn)

Я считаю, что часто полезно использовать forever из Control.Monad или один из комбинаторов из Pipe.Prelude вместо ручной рекурсии; это помогает сократить количество мелких опечаток, подобных этой. Однако, как говорят дети, ваш пробег может очень сильно варьироваться.

person hao    schedule 30.05.2017
comment
Я не думал о декомпозиции проблемы, как вы. Очень хорошо. Вероятно, это вопрос для другого SO-вопроса, но при таком подходе мы можем достичь предела открытых каталогов, поскольку мы посещаем их в порядке поиска в глубину, верно? - person Damian Nadales; 31.05.2017
comment
@DamianNadales Я не думаю, что каталоги так работают. Вы не открываете его, не получаете дескриптор и не используете его как курсор для отображения его содержимого. У вас просто есть путь к файлу и вы делаете системный вызов, который дает вам список содержимого этого пути на момент выполнения системного вызова. - person Carl; 31.05.2017
comment
Я думал, что использую поток каталогов в качестве курсора, так как я открываю его и перебираю его содержимое, используя: readDirStream stream... - person Damian Nadales; 31.05.2017